【问题标题】:Exposing inner Control properties for binding in WPF公开内部控件属性以在 WPF 中进行绑定
【发布时间】:2011-05-09 07:39:27
【问题描述】:

[编辑]:我自己想出了如何做到这一点。我发布了我的解决方案,希望它可以节省其他人几天的谷歌搜索。如果您是 WPF 专家,请查看我的解决方案,让我知道是否有更好/更优雅/更有效的方法来做到这一点。特别是,我有兴趣知道我不知道的东西......这个解决方案将如何让我陷入困境?问题归结为暴露内部控件属性。

问题: 我正在创建一些代码以在 WPF 中为 XML 文件自动生成数据绑定 GUI。我有一个 xsd 文件,可以帮助我确定节点类型等。简单的 Key/Value 元素很容易。

当我解析这个元素时:

<Key>value</Key>

我可以创建一个新的“KeyValueControl”并将DataContext 设置为此元素。 KeyValueControl 被定义为 UserControl 并且上面有一些简单的绑定。它适用于任何简单的 XElement。

此控件中的 XAML 如下所示:

<Label Content={Binding Path=Name} /> 
<TextBox Text={Binding Path=Value} />

结果是一行有一个带有元素名称的标签和一个带有我可以编辑的值的文本框。

现在,有时我需要显示查找值而不是实际值。我想创建一个类似于上述KeyValueControl 的“KeyValueComboBox”,但能够指定(基于文件中的信息)ItemsSourceDisplayMemberPathValueMemberPath。 'DisplayMemberPath' 和 'ValueMemberPath' 绑定将与 KeyValueControl 相同。

我不知道标准用户控件是否可以处理这个问题,或者我是否需要从Selector 继承。

控件中的 XAML 如下所示:

<Label Content={Binding Path=Name} /> 
<ComboBox SelectedValue={Binding Path=Value}
          ItemsSource={Binding [BOUND TO THE ItemsSource PROPERTY OF THIS CUSTOM CONTROL]
          DisplayMemberPath={Binding [BOUND TO THE DisplayMemberPath OF THIS CUSTOM CONTROL]
          SelectedValuePath={Binding [BOUND TO THE SelectedValuePath OF THIS CUSTOM CONTROL]/>

在我的代码中,我会做这样的事情(假设这个节点是一个“事物”并且需要显示一个事物列表以便用户可以选择 ID:

var myBoundComboBox = new KeyValueComboBox();
myBoundComboBox.ItemsSource = getThingsList();
myBoundComboBox.DisplayMemberPath = "ThingName";
myBoundComboBox.ValueMemberPath = "ThingID"
myBoundComboBox.DataContext = thisXElement;
...
myStackPanel.Children.Add(myBoundComboBox)

所以我的问题是:

1) 我应该从ControlSelector 继承我的KeyValueComboBox 吗?

2) 如果我应该从Control 继承,我如何公开内部组合框的ItemsSourceDisplayMemberPathValueMemberPath 进行绑定?

3)如果我需要从 Selector 继承,有人可以提供一个小例子来说明我如何开始使用它吗?再说一次,我是 WPF 的新手,所以如果这是我需要走的路,那么一个不错的简单示例将非常有帮助。

【问题讨论】:

    标签: wpf binding user-controls combobox


    【解决方案1】:

    我最终想出了如何自己做到这一点。我在这里发布答案,以便其他人可以看到有效的解决方案,也许 WPF 专家会过来告诉我一个更好/更优雅的方法来做到这一点。

    所以,答案最终是#2。暴露内部属性被证明是正确的答案。设置它实际上很容易......一旦你知道如何去做。没有太多完整的例子(我能找到),所以希望这个例子能帮助遇到这个问题的其他人。

    ComboBoxWithLabel.xaml.cs

    此文件中重要的一点是 DependencyProperties 的使用。请注意,我们现在所做的只是公开属性(LabelContentItemsSource)。 XAML 将负责将内部控件的属性连接到这些外部属性。

    namespace BoundComboBoxExample
    {
        /// <summary>
        /// Interaction logic for ComboBoxWithLabel.xaml
        /// </summary>
        public partial class ComboBoxWithLabel : UserControl
        {
            // Declare ItemsSource and Register as an Owner of ComboBox.ItemsSource
            // the ComboBoxWithLabel.xaml will bind the ComboBox.ItemsSource to this
            // property
            public IEnumerable ItemsSource
            {
                get { return (IEnumerable)GetValue(ItemsSourceProperty); }
                set { SetValue(ItemsSourceProperty, value); }
            }
    
            public static readonly DependencyProperty ItemsSourceProperty =
              ComboBox.ItemsSourceProperty.AddOwner(typeof(ComboBoxWithLabel));
    
            // Declare a new LabelContent property that can be bound as well
            // The ComboBoxWithLable.xaml will bind the Label's content to this
            public string LabelContent
            {
                get { return (string)GetValue(LabelContentProperty); }
                set { SetValue(LabelContentProperty, value); }
            }
    
            public static readonly DependencyProperty LabelContentProperty =
              DependencyProperty.Register("LabelContent", typeof(string), typeof(ComboBoxWithLabel));
          
            public ComboBoxWithLabel()
            {
                InitializeComponent();
            }
        }
    }
    

    ComboBoxWithLabel.xaml

    XAML 非常简单,除了 Label 和 ComboBox ItemsSource 上的绑定。我发现让这些绑定正确的最简单方法是在 .cs 文件中声明属性(如上),然后使用 VS2010 设计器从属性窗格设置绑定源。本质上,这是我所知道的将内部控件的属性绑定到基本控件的唯一方法。如果有更好的方法,请告诉我。

    <UserControl x:Class="BoundComboBoxExample.ComboBoxWithLabel"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 d:DesignHeight="28" d:DesignWidth="453" xmlns:my="clr-namespace:BoundComboBoxExample">
        <Grid>
            <DockPanel LastChildFill="True">
                <!-- This will bind the Content property on the label to the 'LabelContent' 
                     property on this control-->
                <Label Content="{Binding Path=LabelContent, 
                                 RelativeSource={RelativeSource FindAncestor, 
                                                 AncestorType=my:ComboBoxWithLabel, 
                                                 AncestorLevel=1}}" 
                       Width="100" 
                       HorizontalAlignment="Left"/>
                <!-- This will bind the ItemsSource of the ComboBox to this 
                     control's ItemsSource property -->
                <ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, 
                                        AncestorType=my:ComboBoxWithLabel, 
                                        AncestorLevel=1}, 
                                        Path=ItemsSource}"></ComboBox>
                <!-- you can do the same thing with SelectedValuePath, 
                     DisplayMemberPath, etc, but this illustrates the technique -->
            </DockPanel>
                
        </Grid>
    </UserControl>
    

    MainWindow.xaml

    使用它的 XAML 一点也不有趣。这正是我想要的。您可以通过所有标准 WPF 技术设置ItemsSourceLabelContent

    <Window x:Class="BoundComboBoxExample.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="86" Width="464" xmlns:my="clr-namespace:BoundComboBoxExample"
            Loaded="Window_Loaded">
        <Window.Resources>
            <ObjectDataProvider x:Key="LookupValues" />
        </Window.Resources>
        <Grid>
            <my:ComboBoxWithLabel LabelContent="Foo"
                                  ItemsSource="{Binding Source={StaticResource LookupValues}}"
                                  HorizontalAlignment="Left" 
                                  Margin="12,12,0,0" 
                                  x:Name="comboBoxWithLabel1" 
                                  VerticalAlignment="Top" 
                                  Height="23" 
                                  Width="418" />
        </Grid>
    </Window>
    

    为了完整起见,这里是 MainWindow.xaml.cs

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            ((ObjectDataProvider)FindResource("LookupValues")).ObjectInstance =
                (from i in Enumerable.Range(0, 5)
                 select string.Format("Bar {0}", i)).ToArray();
    
        }
    }
    

    【讨论】:

    • 我使用了类似的技术,但是当我的项目本身设置为使用 RelativeSource 绑定时遇到了麻烦。因此,在您的情况下,如果您将一组 ComboBoxItem 元素分配为您的 ItemsSource,并且通过使用 RelativeSource 绑定到 FindAncestor 来绑定到逻辑树上的某些内容,以便绑定到该祖先元素上定义的属性,则绑定将失败。
    • 我只是将它用于一个非常相似的案例,很好的答案。在我的应用程序中,我绑定到 Combobox 的 SelectedItem。只需要注意,为了让绑定正常工作,在某些情况下需要在绑定中添加Mode=twoway和UpdateSourceTrigger=PropertyChanged
    【解决方案2】:

    我尝试了您的解决方案,但对我来说失败了。它根本不会将值传递给内部控制。我所做的是在外部控件中声明相同的依赖属性并将内部绑定到外部:

        // Declare IsReadOnly property and Register as an Owner of TimePicker (base InputBase).IsReadOnly the TimePickerEx.xaml will bind the TimePicker.IsReadOnly to this property
        // does not work: public static readonly DependencyProperty IsReadOnlyProperty = InputBase.IsReadOnlyProperty.AddOwner(typeof(TimePickerEx));
    
        public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register("IsReadOnly", typeof (bool), typeof (TimePickerEx), new PropertyMetadata(default(bool)));
        public bool IsReadOnly
        {
            get { return (bool) GetValue(IsReadOnlyProperty); }
            set { SetValue(IsReadOnlyProperty, value); }
        }
    

    比在 xaml 中:

      <UserControl x:Class="CBRControls.TimePickerEx" x:Name="TimePickerExControl"
            ...
            >
    
          <xctk:TimePicker x:Name="Picker" 
                  IsReadOnly="{Binding ElementName=TimePickerExControl, Path=IsReadOnly}"
                  ...
           />
    
      </UserControl>
    

    【讨论】:

      猜你喜欢
      • 2015-04-01
      • 2013-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-26
      相关资源
      最近更新 更多