在WPF中暴露内部控件属性以进行绑定。

问题描述 投票:44回答:2

[编辑]。 我自己想出了办法。我把我的解决方法贴出来,希望能让别人省去几天的上网时间。如果你是WPF大师,请看看我的解决方案,如果有更好更优雅更有效的方法,请告诉我。 特别是,我很想知道我不知道的是,这个解决方案怎么会把我往下搞?这个问题其实归根结底就是暴露内部的控件属性。

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

当我解析这个元素的时候。

<Key>value</Key>

我可以创建一个新的 "KeyValueControl",并设置为: DataContext 到这个元素。的 KeyValueControl 被定义为 UserControl 而只是在上面有一些简单的绑定。对于任何简单的XElement来说,它都非常好用。

这个控件里面的XAML是这样的。

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

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

现在,有的时候我需要显示查找值而不是实际值。我想创建一个类似于上面的 "KeyValueComboBox"。KeyValueControl 但能够指定(根据文件中的信息)的。ItemsSource, DisplayMemberPathValueMemberPath. DisplayMemberPath "和 "ValueMemberPath "的绑定将与 "DisplayMemberPath "相同。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) 我是否应该继承我的 KeyValueComboBoxControlSelector?

2) 如果我应该继承 Control,我如何暴露组合盒内部的? ItemsSource, DisplayMemberPathValueMemberPath 进行绑定?

3) 如果我需要从Selector继承,谁能提供一个小例子说明我如何开始? 同样,我是WPF的新手,所以如果我需要走这条路的话,一个好的、简单的例子真的会有帮助。

wpf binding user-controls combobox
2个回答
51
投票

我最后是自己想办法怎么做的。我把答案发在这里,是为了让别人看到一个可行的解决方案,也许会有WPF大师来告诉我一个更好更优雅的方法。

所以,最后的答案是第2条。暴露内部属性原来是正确的答案。设置其实很简单......只要你知道怎么做。没有多少完整的例子(我能找到),所以希望这个例子能帮助其他遇到这个问题的人。

ComboBoxWithLabel.xaml.cs

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections;

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一点都不有趣......这正是我想要的。你可以设置 ItemsSourceLabelContent 通过所有标准的WPF技术。

<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();

    }
}

1
投票

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

    // 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>
© www.soinside.com 2019 - 2024. All rights reserved.