ContentControl с конвертером автоматически UpdateSource - PullRequest
0 голосов
/ 03 февраля 2020

Как мне обновить источник ContentControl 'Content Binding?

<ContentControl Content="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={local:TypeMappingConverter}, Mode=TwoWay}" />

Я собираюсь написать приложение, в котором во время выполнения я хочу аннотировать C# -Введите и сохраните это в файл и перезагрузите его. Это скриншот моего пользовательского интерфейса:

Screenshot

Слева Пользователь может выбрать из доступных типов, а справа информация выбранного типа: показывается через ContentControl .

Это мой класс Model:

public class TypeMapping
{
    public Type MappedType
    {
        get => Type.GetType(MappedTypeName);
        set => MappedTypeName = value.FullName;
    }

    public string MappedTypeName { get; set; }

    public IEnumerable<PropertyMapping> MappedProperties { get; set; } = Array.Empty<PropertyMapping>();

    public virtual string SomeText { get; set; }
}

Я хочу сохранить только сопоставленные свойства, но не все доступные свойства. Так что мой TypeMapping преобразуется в TypeMappingViewModel:

public class TypeMappingViewModel : TypeMapping, INotifyPropertyChanged
{
    public IEnumerable<PropertyMapping> AvailableProperties { get; set; }

    public override string SomeText 
    { 
        get => base.SomeText; 
        set { base.SomeText = value; NotifyPropertyChanged(); }
    }

    public TypeMappingViewModel(TypeMapping from)
    {
        MappedTypeName = from.MappedTypeName;
        MappedProperties = from.MappedProperties;
        AvailableProperties = MappedType.GetProperties().Select(pi => new PropertyMapping { PropertyName = pi.Name });
        SomeText = from.SomeText;
    }

    public TypeMapping ToTypeMapping()
    {
        return new TypeMapping
        {
            MappedProperties = MappedProperties,
            MappedTypeName = MappedTypeName,
            SomeText = SomeText
        };
    }

    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

Это все другие классы:

public class MainViewModel
{
    public ObservableCollection<TypeMapping> MappedTypes { get; set; }
        = new ObservableCollection<TypeMapping>(new[]
        {
            new TypeMapping { MappedTypeName = "System.Threading.Tasks.Task" },
            new TypeMapping { MappedTypeName = "System.Type" }
        });

    public TypeMapping SelectedType { get; set; }
}

public class PropertyMapping
{
    public string PropertyName { get; set; }

    public string SomeText { get; set; }
}

public partial class MainWindow : Window
{
    public MainViewModel ViewModel { get; } = new MainViewModel();

    public MainWindow()
    {
        InitializeComponent();
    }
}

public class TypeMappingConverter : MarkupExtension, IValueConverter
{
    #region IValueConverter

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is TypeMapping typeMapping)
            return new TypeMappingViewModel(typeMapping);
        else
            return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is TypeMappingViewModel typeMappingViewModel)
            return typeMappingViewModel.ToTypeMapping();
        else
            return value;
    }

    #endregion

    #region MarkupExtension

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }

    #endregion
}

и XAML:

<Window x:Class="ListViewHowTo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ListViewHowTo"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <Style TargetType="FrameworkElement" x:Key="baseStyle">
            <Setter Property="Margin" Value="3" />
        </Style>
    </Window.Resources>

    <DockPanel>
        <ListBox ItemsSource="{Binding ViewModel.MappedTypes, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
                 SelectedItem="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
                 DisplayMemberPath="MappedTypeName" 
                 Style="{StaticResource baseStyle}"/>
        <ContentControl Content="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={local:TypeMappingConverter}, Mode=TwoWay}">
            <ContentControl.ContentTemplate>
                <DataTemplate DataType="{x:Type local:TypeMappingViewModel}">
                    <StackPanel Orientation="Vertical">
                        <TextBlock Text="{Binding MappedTypeName}" Style="{StaticResource baseStyle}" />
                        <TextBox Text="{Binding SomeText}" Style="{StaticResource baseStyle}" />
                        <ListView ItemsSource="{Binding AvailableProperties}" Style="{StaticResource baseStyle}">
                            <ListView.View>
                                <GridView>
                                    <GridViewColumn Header="PropertyName" DisplayMemberBinding="{Binding PropertyName}" />
                                    <GridViewColumn Header="SomeText" DisplayMemberBinding="{Binding SomeText}" />
                                </GridView>
                            </ListView.View>
                        </ListView>
                    </StackPanel>
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </DockPanel>
</Window>

Значение правильно конвертируется. Но как мне запустить ConvertBack? В противном случае мой введенный текст будет потерян после изменения выбранного типа. Мне нужен ContentControl, потому что позже будут разные типы сопоставления с разными видами, которые я хочу выбрать с помощью TemplateSelector.

1 Ответ

0 голосов
/ 03 февраля 2020

ItemsSource объекта ListBox должен быть привязан к коллекции экземпляров модели представления вместо коллекции экземпляров модели, которые необходимо преобразовать. Преобразование вообще не требуется.

public class MainViewModel
{
    public ObservableCollection<TypeMappingViewModel> MappedTypes { get; }
        = new ObservableCollection<TypeMappingViewModel>
    {
        new TypeMappingViewModel { MappedTypeName = "System.Threading.Tasks.Task" },
        new TypeMappingViewModel { MappedTypeName = "System.Type" }
    };

    public TypeMappingViewModel SelectedType { get; set; }
}

С

DataContext = ViewModel;

в конструкторе MainWindow, вы бы привязали Content таким образом, и ввод текста в TextBlock непосредственно установит SomeText свойство соответствующего вида модели элемента:

<ContentControl Content="{Binding SelectedType}">
    ...
    <TextBox Text="{Binding SomeText}" />
    ...
</ContentControl>
...