使用 DependencyProperty 在 CustomControl 中绑定 ComboBox 的 SelectedItem 的问题
Issues Binding SelectedItem of ComboBox in a CustomControl using DependencyProperty
我正在 WPF 中创建一个自定义控件,其中包含一个文本框、图像按钮和一个组合框。我能够正确布局所有内容,并且所有绑定都适用于除组合框的 SelectedItem 之外的所有内容。
自定义控件代码如下:
public class GelPakPickerOverlay : Border
{
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location",
typeof(string),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(string.Empty, OnLocationChanged));
public static readonly DependencyProperty GelPakSourceProperty =
DependencyProperty.Register(
"GelPakSource",
typeof(IEnumerable),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnGelPakSourceChanged));
public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register(
"SaveCommand",
typeof(ICommand),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSaveCommandChanged));
private static ComboBox gpSelector;
private static TextBox gpLocation;
private static Button saveButton;
public GelPakPickerOverlay()
{
Height = 98;
gpSelector = new ComboBox();
gpSelector.Width = 100;
gpSelector.Margin = new Thickness(10);
gpSelector.HorizontalAlignment = HorizontalAlignment.Left;
gpSelector.VerticalAlignment = VerticalAlignment.Center;
Grid grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition());
ColumnDefinition def = new ColumnDefinition();
def.Width = new GridLength(40);
grid.ColumnDefinitions.Add(def);
gpLocation = new TextBox();
gpLocation.Style = (Style) FindResource("TextBoxStyleBase");
gpLocation.Width = 70;
gpLocation.Margin = new Thickness(10);
gpLocation.HorizontalAlignment = HorizontalAlignment.Left;
gpLocation.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(gpLocation, 0);
saveButton = new Button();
saveButton.Style = (Style) FindResource("SaveButton");
saveButton.Margin = new Thickness(0, 10, 10, 10);
saveButton.HorizontalAlignment = HorizontalAlignment.Center;
Grid.SetColumn(saveButton, 1);
grid.Children.Add(gpLocation);
grid.Children.Add(saveButton);
StackPanel mainChild = new StackPanel();
mainChild.Orientation = Orientation.Vertical;
mainChild.Children.Add(gpSelector);
mainChild.Children.Add(grid);
Child = mainChild;
}
public object SelectedGelPak
{
get { return GetValue(SelectedGelPakProperty); }
set { SetValue(SelectedGelPakProperty, value); }
}
public string Location
{
get { return GetValue(LocationProperty).ToString(); }
set { SetValue(LocationProperty, value); }
}
public IEnumerable GelPakSource
{
get { return (IEnumerable) GetValue(GelPakSourceProperty); }
set { SetValue(GelPakSourceProperty, value); }
}
public ICommand SaveCommand
{
get { return (ICommand) GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private static void OnLocationChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpLocation != null)
{
gpLocation.Text = e.NewValue.ToString();
}
}
private static void OnGelPakSourceChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpSelector != null)
{
gpSelector.ItemsSource = (IEnumerable) e.NewValue;
}
}
private static void OnSaveCommandChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (saveButton != null)
{
saveButton.Command = (ICommand) e.NewValue;
}
}
}
这是它在主要内容中的引用方式 window:
<ctl:GelPakPickerOverlay
Width="132"
DockPanel.Dock="Right"
VerticalAlignment="Bottom"
Background="{StaticResource primaryBrush}"
BorderBrush="{StaticResource accentBrushOne}"
BorderThickness="2,2,0,0"
Visibility="{
Binding GelPakPickerViewModel.IsPickerVisible,
Converter={StaticResource BoolToHiddenVisConverter},
FallbackValue=Visible}"
GelPakSource="{Binding GelPakPickerViewModel.GelPakList}"
SelectedGelPak="{Binding GelPakPickerViewModel.SelectedGelPak}"
Location="{Binding GelPakPickerViewModel.GelPakLocation, UpdateSourceTrigger=LostFocus}"
SaveCommand="{Binding GelPakPickerViewModel.UpdateGpDataCommand}"/>
此 window 的数据上下文是 MainWindowViewModel,它有一个 GelPakPickerViewModel 属性,所有绑定都连接到该模型。 "Location"、"GelPakSource" 和 "SaveCommand" 属性都正常工作,并按照我期望的方式将所有内容路由到 GelPakPickerViewModel。但是,当您 select 组合框中的任何内容时,它实际上从未进入 GelPakViewModels SelectedGelPak 属性(属于 GelPak 类型)。
这是怎么回事?有没有人有解决这个问题的建议?!?
编辑:我向 SelectedGelPakProperty 添加了一个 属性 更改的事件侦听器,如下所示:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSelectedGelPakChanged));
........
private static void OnSelectedGelPakChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpLocation != null)
{
gpSelector.SelectedItem = e.NewValue;
}
}
但这仍然没有真正改变视图模型中的 SelectedGelPak 对象。
您没有指定在 SelectedGelPak 更改其值时分配的任何操作
(仅 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)。
添加
new FrameworkPropertyMetadata(null, OnSelectedGelPakChanged));
并在此方法中将 SelectedGelPak 分配给 gpSelector.SelectedItem
编辑:
老实说,您的代码看起来很糟糕,因为您将视觉声明和逻辑都放在一个 class 中。您有 .xaml 文件来声明您的 class 的样子,并且 .xaml.cs 用于某些逻辑。然后将它们分开如下:
XAML:
<StackPanel Name="MainPanel">
<ComboBox SelectedItem="{Binding SelectedGelPak}"
ItemsSource="{Binding GelPakSource}"/>
<TextBox Text="{Binding Location}"/>
<Button Command="{Binding SaveCommand}"/>
</StackPanel>
.XAML.CS:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location",
typeof(string),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(string.Empty, OnLocationChanged));
public static readonly DependencyProperty GelPakSourceProperty =
DependencyProperty.Register(
"GelPakSource",
typeof(IEnumerable),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnGelPakSourceChanged));
public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register(
"SaveCommand",
typeof(ICommand),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSaveCommandChanged));
public GelPakPickerOverlay()
{
this.MainPanel.DataContext = this;
}
public object SelectedGelPak
{
get { return GetValue(SelectedGelPakProperty); }
set { SetValue(SelectedGelPakProperty, value); }
}
public string Location
{
get { return GetValue(LocationProperty).ToString(); }
set { SetValue(LocationProperty, value); }
}
public IEnumerable GelPakSource
{
get { return (IEnumerable) GetValue(GelPakSourceProperty); }
set { SetValue(GelPakSourceProperty, value); }
}
public ICommand SaveCommand
{
get { return (ICommand) GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
}
在这种情况下至关重要的是构造函数。您的 StackPanel 的 DataContext 指向您的代码隐藏文件,因此 StackPanel 中的元素可以毫不费力地访问声明的依赖属性,但整个 GelPakPickerOverlay 的 DataContext 仍然基于父级,因此没有任何改变。试试吧。
SelectedGelPak
绑定应该是双向的。
要么你设置Binding.Mode
属性,喜欢
SelectedGelPak="{Binding GelPakPickerViewModel.SelectedGelPak, Mode=TwoWay}"
或者通过在 属性 元数据中设置相应的标志,使 SelectedGelPak
属性 默认双向绑定:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); // here
编辑:您现在可以直接将内部 ComboBox 的 SelectedItem
属性 绑定到 SelectedGelPak
[=28],而不是使用 PropertyChangedCallback (OnSelectedGelPakChanged
) =]:
<ComboBox ... SelectedItem="{Binding SelectedGelPak,
RelativeSource={RelativeSource AncestorType=local:GelPakPickerOverlay}}"/>
您正在监听 ViewModel 属性 的变化,但这只是数据流动的方式之一。您需要在 Combo 中监听视图的变化。
为此,请像这样订阅其 SelectionChanged
事件:
gpSelector = new ComboBox();
gpSelector.Width = 100;
gpSelector.Margin = new Thickness(10);
gpSelector.HorizontalAlignment = HorizontalAlignment.Left;
gpSelector.VerticalAlignment = VerticalAlignment.Center;
gpSelector.SelectionChanged += OnGpSelectorSelectionChanged;
然后在您的事件处理程序中,相应地更改您的 DependencyProperty 的值:
private void OnGpSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SetCurrentValue(SelectedGelPakProperty, gpSelector.SelectedItem);
}
这样您就支持了 ViewModel 和 Control 之间的双向通信。
我正在 WPF 中创建一个自定义控件,其中包含一个文本框、图像按钮和一个组合框。我能够正确布局所有内容,并且所有绑定都适用于除组合框的 SelectedItem 之外的所有内容。
自定义控件代码如下:
public class GelPakPickerOverlay : Border
{
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location",
typeof(string),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(string.Empty, OnLocationChanged));
public static readonly DependencyProperty GelPakSourceProperty =
DependencyProperty.Register(
"GelPakSource",
typeof(IEnumerable),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnGelPakSourceChanged));
public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register(
"SaveCommand",
typeof(ICommand),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSaveCommandChanged));
private static ComboBox gpSelector;
private static TextBox gpLocation;
private static Button saveButton;
public GelPakPickerOverlay()
{
Height = 98;
gpSelector = new ComboBox();
gpSelector.Width = 100;
gpSelector.Margin = new Thickness(10);
gpSelector.HorizontalAlignment = HorizontalAlignment.Left;
gpSelector.VerticalAlignment = VerticalAlignment.Center;
Grid grid = new Grid();
grid.ColumnDefinitions.Add(new ColumnDefinition());
ColumnDefinition def = new ColumnDefinition();
def.Width = new GridLength(40);
grid.ColumnDefinitions.Add(def);
gpLocation = new TextBox();
gpLocation.Style = (Style) FindResource("TextBoxStyleBase");
gpLocation.Width = 70;
gpLocation.Margin = new Thickness(10);
gpLocation.HorizontalAlignment = HorizontalAlignment.Left;
gpLocation.VerticalAlignment = VerticalAlignment.Center;
Grid.SetColumn(gpLocation, 0);
saveButton = new Button();
saveButton.Style = (Style) FindResource("SaveButton");
saveButton.Margin = new Thickness(0, 10, 10, 10);
saveButton.HorizontalAlignment = HorizontalAlignment.Center;
Grid.SetColumn(saveButton, 1);
grid.Children.Add(gpLocation);
grid.Children.Add(saveButton);
StackPanel mainChild = new StackPanel();
mainChild.Orientation = Orientation.Vertical;
mainChild.Children.Add(gpSelector);
mainChild.Children.Add(grid);
Child = mainChild;
}
public object SelectedGelPak
{
get { return GetValue(SelectedGelPakProperty); }
set { SetValue(SelectedGelPakProperty, value); }
}
public string Location
{
get { return GetValue(LocationProperty).ToString(); }
set { SetValue(LocationProperty, value); }
}
public IEnumerable GelPakSource
{
get { return (IEnumerable) GetValue(GelPakSourceProperty); }
set { SetValue(GelPakSourceProperty, value); }
}
public ICommand SaveCommand
{
get { return (ICommand) GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
private static void OnLocationChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpLocation != null)
{
gpLocation.Text = e.NewValue.ToString();
}
}
private static void OnGelPakSourceChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpSelector != null)
{
gpSelector.ItemsSource = (IEnumerable) e.NewValue;
}
}
private static void OnSaveCommandChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (saveButton != null)
{
saveButton.Command = (ICommand) e.NewValue;
}
}
}
这是它在主要内容中的引用方式 window:
<ctl:GelPakPickerOverlay
Width="132"
DockPanel.Dock="Right"
VerticalAlignment="Bottom"
Background="{StaticResource primaryBrush}"
BorderBrush="{StaticResource accentBrushOne}"
BorderThickness="2,2,0,0"
Visibility="{
Binding GelPakPickerViewModel.IsPickerVisible,
Converter={StaticResource BoolToHiddenVisConverter},
FallbackValue=Visible}"
GelPakSource="{Binding GelPakPickerViewModel.GelPakList}"
SelectedGelPak="{Binding GelPakPickerViewModel.SelectedGelPak}"
Location="{Binding GelPakPickerViewModel.GelPakLocation, UpdateSourceTrigger=LostFocus}"
SaveCommand="{Binding GelPakPickerViewModel.UpdateGpDataCommand}"/>
此 window 的数据上下文是 MainWindowViewModel,它有一个 GelPakPickerViewModel 属性,所有绑定都连接到该模型。 "Location"、"GelPakSource" 和 "SaveCommand" 属性都正常工作,并按照我期望的方式将所有内容路由到 GelPakPickerViewModel。但是,当您 select 组合框中的任何内容时,它实际上从未进入 GelPakViewModels SelectedGelPak 属性(属于 GelPak 类型)。
这是怎么回事?有没有人有解决这个问题的建议?!?
编辑:我向 SelectedGelPakProperty 添加了一个 属性 更改的事件侦听器,如下所示:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSelectedGelPakChanged));
........
private static void OnSelectedGelPakChanged(
DependencyObject source, DependencyPropertyChangedEventArgs e)
{
if (gpLocation != null)
{
gpSelector.SelectedItem = e.NewValue;
}
}
但这仍然没有真正改变视图模型中的 SelectedGelPak 对象。
您没有指定在 SelectedGelPak 更改其值时分配的任何操作 (仅 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)。 添加
new FrameworkPropertyMetadata(null, OnSelectedGelPakChanged));
并在此方法中将 SelectedGelPak 分配给 gpSelector.SelectedItem
编辑:
老实说,您的代码看起来很糟糕,因为您将视觉声明和逻辑都放在一个 class 中。您有 .xaml 文件来声明您的 class 的样子,并且 .xaml.cs 用于某些逻辑。然后将它们分开如下:
XAML:
<StackPanel Name="MainPanel">
<ComboBox SelectedItem="{Binding SelectedGelPak}"
ItemsSource="{Binding GelPakSource}"/>
<TextBox Text="{Binding Location}"/>
<Button Command="{Binding SaveCommand}"/>
</StackPanel>
.XAML.CS:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static readonly DependencyProperty LocationProperty =
DependencyProperty.Register(
"Location",
typeof(string),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(string.Empty, OnLocationChanged));
public static readonly DependencyProperty GelPakSourceProperty =
DependencyProperty.Register(
"GelPakSource",
typeof(IEnumerable),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnGelPakSourceChanged));
public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register(
"SaveCommand",
typeof(ICommand),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(null, OnSaveCommandChanged));
public GelPakPickerOverlay()
{
this.MainPanel.DataContext = this;
}
public object SelectedGelPak
{
get { return GetValue(SelectedGelPakProperty); }
set { SetValue(SelectedGelPakProperty, value); }
}
public string Location
{
get { return GetValue(LocationProperty).ToString(); }
set { SetValue(LocationProperty, value); }
}
public IEnumerable GelPakSource
{
get { return (IEnumerable) GetValue(GelPakSourceProperty); }
set { SetValue(GelPakSourceProperty, value); }
}
public ICommand SaveCommand
{
get { return (ICommand) GetValue(SaveCommandProperty); }
set { SetValue(SaveCommandProperty, value); }
}
}
在这种情况下至关重要的是构造函数。您的 StackPanel 的 DataContext 指向您的代码隐藏文件,因此 StackPanel 中的元素可以毫不费力地访问声明的依赖属性,但整个 GelPakPickerOverlay 的 DataContext 仍然基于父级,因此没有任何改变。试试吧。
SelectedGelPak
绑定应该是双向的。
要么你设置Binding.Mode
属性,喜欢
SelectedGelPak="{Binding GelPakPickerViewModel.SelectedGelPak, Mode=TwoWay}"
或者通过在 属性 元数据中设置相应的标志,使 SelectedGelPak
属性 默认双向绑定:
public static readonly DependencyProperty SelectedGelPakProperty =
DependencyProperty.Register(
"SelectedGelPak",
typeof(object),
typeof(GelPakPickerOverlay),
new FrameworkPropertyMetadata(
null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); // here
编辑:您现在可以直接将内部 ComboBox 的 SelectedItem
属性 绑定到 SelectedGelPak
[=28],而不是使用 PropertyChangedCallback (OnSelectedGelPakChanged
) =]:
<ComboBox ... SelectedItem="{Binding SelectedGelPak,
RelativeSource={RelativeSource AncestorType=local:GelPakPickerOverlay}}"/>
您正在监听 ViewModel 属性 的变化,但这只是数据流动的方式之一。您需要在 Combo 中监听视图的变化。
为此,请像这样订阅其 SelectionChanged
事件:
gpSelector = new ComboBox();
gpSelector.Width = 100;
gpSelector.Margin = new Thickness(10);
gpSelector.HorizontalAlignment = HorizontalAlignment.Left;
gpSelector.VerticalAlignment = VerticalAlignment.Center;
gpSelector.SelectionChanged += OnGpSelectorSelectionChanged;
然后在您的事件处理程序中,相应地更改您的 DependencyProperty 的值:
private void OnGpSelectorSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SetCurrentValue(SelectedGelPakProperty, gpSelector.SelectedItem);
}
这样您就支持了 ViewModel 和 Control 之间的双向通信。