【问题标题】:WPF Two-way binding to property's property to replace the parent propertyWPF 双向绑定到属性的属性以替换父属性
【发布时间】:2017-05-17 06:57:42
【问题描述】:

我有一个使用 DependencyProperties(或 INotifyPropertyChanged)的 ViewModel,它具有非常简单的复合类型(如 System.Windows.Point)的属性。 简单复合类型不使用 DependencyProperties 或 INotifyPropertyChanged,它打算保持这种方式(它不在我的控制范围内)。

我现在要做的是创建与 Point 的 X 和 Y 属性的双向数据绑定,但是当其中一个发生更改时,我希望替换整个 Point 类,而不是只更新会员。

代码示例仅用于说明:

<Window ...>
    <StackPanel>
        <TextBox Text="{Binding TestPoint.X, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
        <TextBox Text="{Binding TestPoint.Y, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
        <!-- make following label update based on textbox changes above -->
        <Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"/>
    </StackPanel>
</Window>

代码隐藏:

public partial class MainWindow : Window
{
    public Point TestPoint
    {
        get { return (Point)GetValue(TestPointProperty); }
        set { SetValue(TestPointProperty, value); }
    }
    public static readonly DependencyProperty TestPointProperty = DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}

我的想法是将两个TextBoxes直接绑定到TestPoint属性并使用IValueConverter仅过滤掉特定成员,但是ConvertBack方法出现问题,因为修改X值时Y值不再存在。

我觉得必须有一个我没有得到的非常简单的解决方案。

编辑:

上面的代码只是一个简化的例子,实际应用比这更复杂。复合类型有大约 7 个成员,并且在整个应用程序中普遍使用,因此将其拆分为单个成员感觉不对。另外我想依靠依赖属性的 OnChanged 事件来调用其他更新,所以我真的需要替换整个类。

【问题讨论】:

  • 你可以在转换器中保存其他值,你试过这样吗?
  • 您的意思是作为转换器的成员吗?我没想到。我假设我需要为每个字段设置单独的转换器实例,对吗?还有安全吗?是否保证Convert期间存储的Y值在执行ConvertBack时仍然有效?
  • 当绑定到XY 属性时,请注意潜在的内存泄漏。由于Point 不是INotifyPropertyChanged 并且它们不是DependencyProperties,绑定将使用PropertyDescriptor,如果绑定不是OneTime,则可能导致泄漏:stackoverflow.com/questions/18542940/…
  • 是的,您需要为每个字段提供单独的转换器实例(从技术上讲,对于每个 x 和 y 字段)。是的,当执行 ConverBack 时,存储的值将有效。

标签: c# wpf data-binding dependency-properties inotifypropertychanged


【解决方案1】:

我认为在这种情况下,引入两个 DependencyProperties 会更容易,一个用于 X 值,一个用于 Y 值并简单地绑定到它们。 在每个 DependencyProperty 的 PropertyMetadata 中注册一个方法,s.th。在每次值更改时,如果您仍然需要,您可以替换您的点对象。

您还可以使用适当的单向 IMultiValueConverter 将标签绑定到两个属性的 MultiBinding。

【讨论】:

  • 抱歉,我无法完全分离这些值。我在原始问题的底部添加了解释。
【解决方案2】:

为什么不使用访问器?

public partial class MainWindow : Window
{
    public Point TestPoint
    {
        get { return (Point)GetValue(TestPointProperty); }
        set { SetValue(TestPointProperty, value); }
    }

    public double TestPointX
    {
        get { return this.TestPoint.X; }
        set
        { 
            SetValue(TestPointProperty, new Point(value, this.TestPointY);
        }
    }

    public double TestPointY
    {
        get { return this.TestPoint.Y; }
        set
        { 
            SetValue(TestPointProperty, new Point(this.TestPointX, value);
        }
    }

    public static readonly DependencyProperty TestPointProperty = DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
}

在你的 XAML 中:

<Window ...>
<StackPanel>
        <TextBox Text="{Binding TestPointX, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
        <TextBox Text="{Binding TestPointY, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, StringFormat=\{0:F\}}"/>
        <Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"/>
    </StackPanel>
</Window>

【讨论】:

  • 对于一个简单问题的解决方案有点冗长,但我认为这将是最安全和可读的解决方案,谢谢!
【解决方案3】:

正如我在 cmets 中所说的,您可以这样尝试。当我们将转换器添加到特定控件的资源时,相同的实例将用于子控件。

  <Grid>
    <Grid.Resources />
    <StackPanel>
        <StackPanel>
            <StackPanel.Resources>
                <local:PointConverter x:Key="PointConverter" />
            </StackPanel.Resources>
            <TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
            <TextBox Text="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
            <!--  make following label update based on textbox changes above  -->
            <Label Content="{Binding TestPoint, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
        </StackPanel>
        <StackPanel>
            <StackPanel.Resources>
                <local:PointConverter x:Key="PointConverter" />
            </StackPanel.Resources>
            <TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=x}" />
            <TextBox Text="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={StaticResource PointConverter}, ConverterParameter=y}" />
            <!--  make following label update based on textbox changes above  -->
            <Label Content="{Binding TestPoint2, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" />
        </StackPanel>
    </StackPanel>
</Grid>

和后面的代码,

    public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }


    public Point TestPoint
    {
        get
        {
            return (Point)GetValue(TestPointProperty);
        }
        set
        {
            SetValue(TestPointProperty, value);
        }
    }

    // Using a DependencyProperty as the backing store for TestPoint.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TestPointProperty =
        DependencyProperty.Register("TestPoint", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));
     public Point TestPoint2
    {
        get
        {
            return (Point)GetValue(TestPoint2Property);
        }
        set
        {
            SetValue(TestPoint2Property, value);
        }
    }

    // Using a DependencyProperty as the backing store for TestPoint.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty TestPoint2Property =
        DependencyProperty.Register("TestPoint2", typeof(Point), typeof(MainWindow), new PropertyMetadata(new Point(1.0, 1.0)));


}

public class PointConverter : IValueConverter
{
    double knownX = 0.0;
    double knownY = 0.0;
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
         if (parameter.ToString() == "x")
        {
            knownX = ((Point)value).X;
            return ((Point)value).X;
        }
        else
        {
            knownY = ((Point)value).Y;
            return ((Point)value).Y;
        }

    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {

         Point p = new Point();
        if (parameter.ToString() == "x")
        {
            p.Y = knownY;
            p.X = double.Parse(value.ToString());
        }
        else
        {
            p.X = knownX;
            p.Y = double.Parse(value.ToString());
        }
        return p;
    }
}

注意:我没有添加任何空检查。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-30
    • 1970-01-01
    • 2011-12-31
    • 2011-02-04
    • 2022-01-24
    • 2011-07-12
    • 2011-03-16
    • 2018-07-24
    相关资源
    最近更新 更多