我在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;
}
}
}
这是在主窗口中引用的方式:
<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}"/>
此窗口的数据上下文是MainWindowViewModel,它具有GelPakPickerViewModel属性,所有绑定都连接到该属性。 “Location”,“GelPakSource”和“SaveCommand”属性都能正常工作,并按照我的预期将所有内容路由到GelPakPickerViewModel。但是,当您从组合框中选择任何内容时,它实际上从未进入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对象。
答案 0 :(得分:2)
您正在收听ViewModel属性的更改,但这只是数据流动的方式之一。您需要在组合中收听视图中的更改。
为此,请订阅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之间的双向通信。
答案 1 :(得分:1)
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的OnSelectedGelPakChanged
属性绑定到SelectedItem
属性,而不是拥有PropertyChangedCallback(SelectedGelPak
):
<ComboBox ... SelectedItem="{Binding SelectedGelPak,
RelativeSource={RelativeSource AncestorType=local:GelPakPickerOverlay}}"/>
答案 2 :(得分:0)
当SelectedGelPak更改其值时,您不指定要分配的任何操作 (仅 FrameworkPropertyMetadataOptions.BindsTwoWayByDefault )。 添加
new FrameworkPropertyMetadata(null, OnSelectedGelPakChanged));
并在此方法中将SelectedGelPak指定给gpSelector.SelectedItem
编辑:
老实说,你的代码看起来很讨厌,因为你在一个类中放置了可视声明和逻辑。您有.xaml文件来声明您的类的外观和.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仍然基于父级,因此没有任何更改。试试吧。