[编辑]。 我自己想出了办法。我把我的解决方法贴出来,希望能让别人省去几天的上网时间。如果你是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
, DisplayMemberPath
和 ValueMemberPath
. 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) 我是否应该继承我的 KeyValueComboBox
从 Control
或 Selector
?
2) 如果我应该继承 Control
,我如何暴露组合盒内部的? ItemsSource
, DisplayMemberPath
和 ValueMemberPath
进行绑定?
3) 如果我需要从Selector继承,谁能提供一个小例子说明我如何开始? 同样,我是WPF的新手,所以如果我需要走这条路的话,一个好的、简单的例子真的会有帮助。
我最后是自己想办法怎么做的。我把答案发在这里,是为了让别人看到一个可行的解决方案,也许会有WPF大师来告诉我一个更好更优雅的方法。
所以,最后的答案是第2条。暴露内部属性原来是正确的答案。设置其实很简单......只要你知道怎么做。没有多少完整的例子(我能找到),所以希望这个例子能帮助其他遇到这个问题的人。
ComboBoxWithLabel.xaml.cs
在这个文件中,最重要的是使用DependencyProperties。 请注意,我们现在所做的只是暴露属性(LabelContent
和 ItemsSource
). 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一点都不有趣......这正是我想要的。你可以设置 ItemsSource
和 LabelContent
通过所有标准的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();
}
}
我尝试了你的解决方案,但它对我来说是失败的。它根本没有将值传递给内部控件。我所做的是在外部控件中声明相同的依赖属性,并将内部与外部绑定。
// 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>