3

我有一個ViewModel使用DependencyProperties(或INotifyPropertyChanged)具有非常簡單的複合類型的屬性,如System.Windows.Point。 簡單的複合類型不使用DependencyProperties或INotifyPropertyChanged,它的目的是保持這種方式(它超出了我的控制範圍)。WPF雙向綁定屬性的屬性,以取代父屬性

我現在想要做的是創建雙向數據綁定到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))); 
} 

我想是兩個文本框直接綁定到測試點屬性,並使用的IValueConverter過濾出特定的成員,但在ConvertBack方法中存在問題,因爲在修改X值時Y值不再存在。

我覺得必須有一個非常簡單的解決方案,我沒有得到。

編輯:

上面的代碼只是一個簡單的例子,實際應用是比這更復雜。複合類型大約有7個成員,並且通常在應用程序中使用,因此將其分解爲單個成員並不合適。此外,我想依賴於屬性的OnChanged事件來調用其他更新,所以我真的需要替換整個類。

+0

你可以保持轉換器內的其他值,你有這樣的嘗試嗎? – WPFUser

+0

你的意思是作爲轉換器的成員?我沒有想到這一點。我假設我需要爲每個字段分開的轉換器實例,對吧?也是安全的?是否保證在ConvertBack執行時存儲的Y值仍然有效? – markyxl

+0

當綁定到'X'和'Y'屬性時,請注意潛在的內存泄漏。由於'Point'不是'INotifyPropertyChanged',並且它們不是'DependencyProperties',綁定將使用'PropertyDescriptor',如果Binding不是'OneTime',則可能導致泄漏:http://stackoverflow.com/questions/18542940/can-bindings-create-memory-leaks-in-wpf – wkl

回答

7

爲什麼不使用存取器?

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> 
+0

對於一個簡單的問題,這是一個有點冗長的解決方案,但我認爲這將是最安全和可讀的解決方案,謝謝! – markyxl

0

我認爲在這種情況下,引入兩個DependencyProperties,一個用於X值,另一個用於Y值,並簡單地綁定到它們會更容易。 在每個DependencyProperty的PropertyMetadata中註冊一個方法s.th.如果您仍然需要,您可以更換每個值更改您的Point對象。

您還可以使用適當的單向IMultiValueConverter將您的標籤綁定到兩個屬性的MultiBinding。

+0

對不起,但我無法完全分離這些值。我在原始問題的底部添加了解釋。 – markyxl

0

正如我在評論中告訴,你可以嘗試這樣的。當我們將轉換器添加到特定控件的資源時,相同的實例將用於子控件。

<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; 
    } 
} 

注:我還沒有添加任何null檢查。