【问题标题】:Issues Binding SelectedItem of ComboBox in a CustomControl using DependencyProperty使用 DependencyProperty 在 CustomControl 中绑定 ComboBox 的 SelectedItem 的问题
【发布时间】:2015-07-08 19:10:45
【问题描述】:

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

【问题讨论】:

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


    【解决方案1】:

    您正在监听 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 之间的双向通信。

    【讨论】:

    • 我认为您已经正确诊断出问题,尽管我认为您的解决方案实际上不会起作用 - 设置 SelectedGetPak 直接调用 setter 中的 SetValue 方法,这会清除现有的绑定属性(即绑定到视图模型属性的属性)。要维护绑定并更新值,应调用SetCurrentValue(SelectedGelPakProperty, gpSelector.SelectedItem)
    • 谢谢你的好先生,你的回答很完美!当 stackoverflow 允许我时,我会在 20 小时内奖励你 50 分赏金。
    • @Grx70 如果绑定设置为Mode=TwoWay(如本例中),则SetValue 不会清除它。但是,是的,一般来说,SetCurrentValue 更可取。我将编辑答案以反映这一点。
    【解决方案2】:

    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 属性,而不是使用 PropertyChangedCallback (OnSelectedGelPakChanged):

    <ComboBox ... SelectedItem="{Binding SelectedGelPak,
        RelativeSource={RelativeSource AncestorType=local:GelPakPickerOverlay}}"/>
    

    【讨论】:

    • 我已经尝试过了,但它不起作用。我应该发布更多可以帮助您的代码吗?
    • 查看编辑后的答案。 AncestorType=Border 也应该可以工作。
    【解决方案3】:

    当 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 仍然基于父级,所以没有任何改变。试试看。

    【讨论】:

    • 感谢您的回复,但是在将属性更改侦听器添加到 SelectedGelPakProperty 后,视图模型属性仍然没有更新。
    猜你喜欢
    • 2013-05-17
    • 2011-11-24
    • 2023-04-03
    • 2013-11-07
    • 2019-11-10
    • 2012-03-26
    • 1970-01-01
    • 2011-11-01
    • 2016-05-15
    相关资源
    最近更新 更多