将ViewModel上的struct属性绑定到WPF工具箱PropertyGrid不会在UI值更改时更新ViewModel

时间:2017-12-25 22:49:23

标签: wpf mvvm data-binding wpftoolkit reactiveui

我正在尝试使用WPF Toolkit PropertyGrid在ReactiveUI中的ViewModel上显示属性。由于Reasons™,其中一个属性是自定义Size结构:

Size.cs

public struct Size
{
    public float Width { get; set; }
    public float Height { get; set; }
}

我的ViewModel公开了哪些内容:

ElementViewModel.cs

public class ElementViewModel : ReactiveObject
{
    private Size _size;
    public Size Size
    {
        get => _size;
        set => this.RaiseAndSetIfChanged(ref _size, value);
    }
}

我使用自定义控件作为编辑器,它只使用两个DoubleUpDowns作为宽度和高度,并手动更新绑定属性:

ElementSizeEditor.xaml

<UserControl x:Class="MyProject.Controls.ElementSizeEditor"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
<Grid>
    <StackPanel Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Left" Orientation="Horizontal">
        <xctk:DoubleUpDown Width="75" TextAlignment="Right" VerticalContentAlignment="Center" 
                           Name="WidthEntry" AllowTextInput="True"
                           ShowButtonSpinner="False" FormatString="0.###" />
        <TextBlock Text="&quot; x " />
        <xctk:DoubleUpDown Width="75" TextAlignment="Right" VerticalContentAlignment="Center" 
                           Name="HeightEntry" AllowTextInput="True" 
                           ShowButtonSpinner="False" FormatString="0.###" />
        <TextBlock Text="&quot;" />
    </StackPanel>
</Grid>
</UserControl>

ElementSizeEditor.xaml.cs

public partial class ElementSizeEditor : UserControl
{
    public static readonly DependencyProperty ValueProperty = 
        DependencyProperty.Register("Value", 
            typeof(Size), 
            typeof(ElementSizeEditor),
            new PropertyMetadata(ValueChanged));

    // manually set the values on the width and height editors when we have a new Size
    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        Size size = (Size)args.NewValue;
        ElementSizeEditor editor = (ElementSizeEditor)d;
        editor.WidthEntry.Value = size.Width;
        editor.HeightEntry.Value = size.Height;
    }

    public Size Value
    {
        get { return (Size)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public ElementSizeEditor()
    {
        InitializeComponent();

        WidthEntry.ValueChanged += ValueChangedHandler;
        HeightEntry.ValueChanged += ValueChangedHandler;
    }

    // manually listen to value changes because we provide a new Size instance for new values
    private void ValueChangedHandler(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        if (WidthEntry.Value.HasValue && HeightEntry.Value.HasValue &&
            (!Core.Util.Math.AreEquivalent((float)WidthEntry.Value, Value.Width) ||
             !Core.Util.Math.AreEquivalent((float)HeightEntry.Value, Value.Height)))
            Value = new Size((float)WidthEntry.Value.Value, (float)HeightEntry.Value.Value);
    }
}

我将ElementSizeEditor注册为app-wide静态资源:

的App.xaml

<Application.Resources>
    <DataTemplate x:Key="ElementSizeEditor">
        <controls:ElementSizeEditor Value="{Binding Path=Value}"/>
    </DataTemplate>
</Application.Resources>

并在我的属性视图中使用它:

ElementPropertiesView.xaml

<UserControl x:Class="MyProject.Views.ElementPropertiesView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit">
<Grid>
    <xctk:PropertyGrid SelectedObject="{Binding}" SelectedObjectName="" SelectedObjectTypeName="{Binding Path=Name}" Grid.Row="0" AutoGenerateProperties="False"
                       ShowAdvancedOptions="False" ShowSortOptions="False" ShowSearchBox="False">
        <xctk:PropertyGrid.EditorDefinitions>
            <xctk:EditorTemplateDefinition TargetProperties="Size" EditingTemplate="{StaticResource ElementSizeEditor}" />
        </xctk:PropertyGrid.EditorDefinitions>
        <xctk:PropertyGrid.PropertyDefinitions>
            <xctk:PropertyDefinition TargetProperties="Size" DisplayName="Size" Description="The Element's size" />
        </xctk:PropertyGrid.PropertyDefinitions>
    </xctk:PropertyGrid>
</Grid>
</UserControl>

在代码隐藏中适当绑定:

ElementPropertiesView.xaml.cs

public partial class ElementPropertiesView : UserControl, IViewFor<ElementViewModel>
{
    public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel",
        typeof(ElementViewModel),
        typeof(ElementPropertiesView));

    public ElementPropertiesView()
    {
        InitializeComponent();
        this.WhenActivated(d =>
        {
            d(this.WhenAnyValue(_ => _.ViewModel).BindTo(this, _ => _.DataContext));
        });
    }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (ColorElementViewModel)value; }
    }

    public ElementViewModel ViewModel
    {
        get { return (ColorElementViewModel) GetValue(ViewModelProperty); }
        set
        {
            SetValue(ViewModelProperty, value);
        }
    }
}

当我加载属性视图时,它正确地将ElementViewModel绑定到PropertyGrid的SelectedObject,正确显示Size属性的ElementSizeEditor,正确绑定到ElementSizeEditor中的值,并在用户时正确更新ValueProperty输入新值。

它没有做的是将新的Size值反馈回ElementViewModel,我很难过为什么。我在其他情况下通过编写一个暴露的辅助类来解决这个问题,并知道如何进入ElementViewModel以更新相关属性,但这些实例更有意义,因为需要额外的功能。这次我只想用新的Size更新ViewModel。

1 个答案:

答案 0 :(得分:0)

这很可能是因为您应该将 DataTemplate 上的绑定模式设置为 TwoWay。也就是说,在 App.xaml 中,您应该将 更改为