如何将不同的 UI 控件绑定到不同的对象
How to bind different UI controls to different objects
我有两个 UI 控件,我想将其属性绑定到两个不同对象的属性。这是我的 XAML 文件:
<Window x:Class="WpfBindingDemo.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"
mc:Ignorable="d" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Canvas Width="300" Height="200">
<Slider x:Name="_slider1" Canvas.Left="10" Canvas.Top="10" Width="272"/>
<Slider x:Name="_slider2" Canvas.Left="10" Canvas.Top="36" Width="272"/>
</Canvas>
</Window>
下面是我的代码:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("MyProperty1");
binding1.Mode = BindingMode.TwoWay;
binding1.Source = _myObject1;
BindingOperations.SetBinding(_slider1, Slider.ValueProperty, binding1);
Binding binding2 = new Binding("MyProperty2");
binding2.Mode = BindingMode.TwoWay;
binding2.Source = _myObject1;
BindingOperations.SetBinding(_slider2, Slider.ValueProperty, binding2);
}
MyClass1 _myObject1 = new MyClass1();
MyClass2 _myObject2 = new MyClass2();
}
public class MyClass1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty1 {get; set}
}
public class MyClass2 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty2 {get; set}
}
}
如您所见,我将 UI 控件属性(在本例中为 Value
)绑定到代码中的不同对象属性(在 window 构造函数中)并且它工作正常,但我发现这个声明太庞大了,我不喜欢它被分成两部分。我想知道在 XAML 中是否有更紧凑的方式来声明这种绑定,比如
<Slider x:Name="_slider1" Value="{Binding MyProperty1, Source=_myObject1}"/>
<Slider x:Name="_slider2" Value="{Binding MyProperty2, Source=_myObject2}"/>
我试过 Source
、RelativeSource
和 ElementName
属性,但没能成功。我错过了什么吗?
如果您将 _myObject1
和 _myObject2
声明为属性(pascal 大小写),则可以绑定它们。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyClass1 MyObject1 { get; } = new MyClass1();
public MyClass2 MyObject2 { get; } = new MyClass2();
}
可以用RelativeSource
引用MainWindow
来绑定。
<Canvas Width="300" Height="200">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Canvas>
您当然也可以将数据上下文设置为 window 本身,这样您就不必每次都使用 RelativeSource
,请参阅@Clemens answer for a code-behind sample .
也可以在 XAML 中设置 DataContext
。
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
然后绑定将以同样的方式简化。
<Canvas Width="300" Height="200" DataContext="{Binding MainWindowViewModel}">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2}"/>
</Canvas>
虽然这可行,但这是一个糟糕的方法。它将用户界面组件 - MainWindow
- 与您的业务数据或逻辑混合在一起。您应该将它们分开以获得更好的可测试性和可维护性。有一种称为 MVVM 的常见模式,它专注于将视图与数据分离。您可以阅读介绍 here.
您应该为主要 window 创建一个通过属性公开数据的视图模型。如果您打算更改属性,您还应该在此处实现 INotifyPropertyChanged
。
public class MainWindowViewModel
{
public MainWindowViewModel()
{
MyObject1 = new MyClass1();
MyObject2 = new MyClass2();
}
public MyClass1 MyObject1 { get; }
public MyClass2 MyObject2 { get; }
}
您可以直接创建视图模型并将其分配为 DataContext
。
<Window x:Class="WpfBindingDemo.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"
mc:Ignorable="d" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<!-- ...other markup. -->
</Window>
或者,您可以创建一个实例并将其分配到 code-behind。
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
然后绑定看起来像这样。
<Canvas Width="300" Height="200">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2}"/>
</Canvas>
附带说明一下,您对 INotifyPropertyChanged
的实现是无用的,除非您实际比较要分配的值是否相等并在 setter 中调用 OnPropertyChanged
。
public class MyClass1 : INotifyPropertyChanged
{
private double _myProperty1;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty1
{
get => _myProperty1;
set
{
if (Math.Abs(_myProperty1 - value) < /* ...your comparison epsilon here. */)
return;
_myProperty1 = value;
OnPropertyChanged();
}
}
}
在浮点数的特定情况下,您应该与 epsilon 进行比较,有关详细信息,请参阅 Comparing double values in C#。
我有两个 UI 控件,我想将其属性绑定到两个不同对象的属性。这是我的 XAML 文件:
<Window x:Class="WpfBindingDemo.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"
mc:Ignorable="d" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Canvas Width="300" Height="200">
<Slider x:Name="_slider1" Canvas.Left="10" Canvas.Top="10" Width="272"/>
<Slider x:Name="_slider2" Canvas.Left="10" Canvas.Top="36" Width="272"/>
</Canvas>
</Window>
下面是我的代码:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfBindingDemo
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Binding binding1 = new Binding("MyProperty1");
binding1.Mode = BindingMode.TwoWay;
binding1.Source = _myObject1;
BindingOperations.SetBinding(_slider1, Slider.ValueProperty, binding1);
Binding binding2 = new Binding("MyProperty2");
binding2.Mode = BindingMode.TwoWay;
binding2.Source = _myObject1;
BindingOperations.SetBinding(_slider2, Slider.ValueProperty, binding2);
}
MyClass1 _myObject1 = new MyClass1();
MyClass2 _myObject2 = new MyClass2();
}
public class MyClass1 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty1 {get; set}
}
public class MyClass2 : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty2 {get; set}
}
}
如您所见,我将 UI 控件属性(在本例中为 Value
)绑定到代码中的不同对象属性(在 window 构造函数中)并且它工作正常,但我发现这个声明太庞大了,我不喜欢它被分成两部分。我想知道在 XAML 中是否有更紧凑的方式来声明这种绑定,比如
<Slider x:Name="_slider1" Value="{Binding MyProperty1, Source=_myObject1}"/>
<Slider x:Name="_slider2" Value="{Binding MyProperty2, Source=_myObject2}"/>
我试过 Source
、RelativeSource
和 ElementName
属性,但没能成功。我错过了什么吗?
如果您将 _myObject1
和 _myObject2
声明为属性(pascal 大小写),则可以绑定它们。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyClass1 MyObject1 { get; } = new MyClass1();
public MyClass2 MyObject2 { get; } = new MyClass2();
}
可以用RelativeSource
引用MainWindow
来绑定。
<Canvas Width="300" Height="200">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
</Canvas>
您当然也可以将数据上下文设置为 window 本身,这样您就不必每次都使用 RelativeSource
,请参阅@Clemens answer for a code-behind sample .
也可以在 XAML 中设置 DataContext
。
<Window ...
DataContext="{Binding RelativeSource={RelativeSource Self}}">
然后绑定将以同样的方式简化。
<Canvas Width="300" Height="200" DataContext="{Binding MainWindowViewModel}">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2}"/>
</Canvas>
虽然这可行,但这是一个糟糕的方法。它将用户界面组件 - MainWindow
- 与您的业务数据或逻辑混合在一起。您应该将它们分开以获得更好的可测试性和可维护性。有一种称为 MVVM 的常见模式,它专注于将视图与数据分离。您可以阅读介绍 here.
您应该为主要 window 创建一个通过属性公开数据的视图模型。如果您打算更改属性,您还应该在此处实现 INotifyPropertyChanged
。
public class MainWindowViewModel
{
public MainWindowViewModel()
{
MyObject1 = new MyClass1();
MyObject2 = new MyClass2();
}
public MyClass1 MyObject1 { get; }
public MyClass2 MyObject2 { get; }
}
您可以直接创建视图模型并将其分配为 DataContext
。
<Window x:Class="WpfBindingDemo.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"
mc:Ignorable="d" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<!-- ...other markup. -->
</Window>
或者,您可以创建一个实例并将其分配到 code-behind。
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
然后绑定看起来像这样。
<Canvas Width="300" Height="200">
<Slider Canvas.Left="10" Canvas.Top="10" Width="272" Value="{Binding MyObject1.MyProperty1}"/>
<Slider Canvas.Left="10" Canvas.Top="36" Width="272" Value="{Binding MyObject2.MyProperty2}"/>
</Canvas>
附带说明一下,您对 INotifyPropertyChanged
的实现是无用的,除非您实际比较要分配的值是否相等并在 setter 中调用 OnPropertyChanged
。
public class MyClass1 : INotifyPropertyChanged
{
private double _myProperty1;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); }
public double MyProperty1
{
get => _myProperty1;
set
{
if (Math.Abs(_myProperty1 - value) < /* ...your comparison epsilon here. */)
return;
_myProperty1 = value;
OnPropertyChanged();
}
}
}
在浮点数的特定情况下,您应该与 epsilon 进行比较,有关详细信息,请参阅 Comparing double values in C#。