使用DependencyProperty

时间:2015-07-08 19:10:45

标签: c# wpf data-binding custom-controls dependency-properties

我在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对象。

3 个答案:

答案 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仍然基于父级,因此没有任何更改。试试吧。