2010-11-12 47 views
39

[編輯]:我想出瞭如何自己做到這一點。我發佈了我的解決方案,希望能在幾天的Google搜索中省去其他人。如果您是WPF大師,請查看我的解決方案,並告訴我是否有更好/更優雅/更高效的方式來執行此操作。特別是,我有興趣知道我不知道的是什麼......這個解決方案將如何讓我走上路?這個問題真的歸結爲暴露內部控制屬性。公開WPF中綁定的內部控件屬性

問題: 我正在創建一些代碼來爲WPF中的XML文件自動生成數據綁定GUI。我有一個xsd文件,可以幫助我確定節點類型等。簡單的鍵/值元素很容易。

當我分析這個元素:

<Key>value</Key> 

我可以創造一個新的「KeyValueControl」,並設置DataContext這個元素。 KeyValue被定義爲一個UserControl,並且只有一些簡單的綁定。它適用於任何簡單的XElement。

此控件中的XAML看起來是這樣的:

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

結果是有該元素的名稱,並與我可以編輯值的文本框標籤的線。

現在,有些時候我需要顯示查找值而不是實際值。我想創建一個類似於上述KeyValueControl的'KeyValueComboBox',但能夠指定(根據文件中的信息)ItemsSource,Display和Value路徑。 「名稱」和「值」綁定將與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? 2)如果我應該從Control繼承,我如何公開內部組合框的ItemsSource,DisplayMemberPath和ValueMemberPath進行綁定? 3)如果我需要從Selector繼承,有人可以提供一個我可以如何開始使用它的小例子嗎?再說一遍,我是WPF的新手,所以如果這是我需要採取的道路,那麼一個很好的簡單示例將非常有用。

回答

47

我最終決定如何自己做到這一點。我在這裏發佈答案,以便其他人可以看到一個有效的解決方案,也許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是非常簡單的,用在標籤和組合框的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技術設置ItemsSource和LabelContent。

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

    } 
} 
+0

我使用了類似的技術,但運行到麻煩時,我的項目本身被設置爲使用RelativeSource綁定。所以在你的情況下,如果你指定了一個ComboBoxItem元素作爲你的ItemsSource的集合,並且通過使用RelativeSource綁定到FindAncestor來綁定到在該anscestor元素上定義的屬性,那麼綁定將失敗。 – jpierson 2010-12-15 04:31:58

+0

我剛剛使用這個非常相似的情況下,很好的答案。在我的應用程序中,我綁定了Combobox的SelectedItem。應該指出的是,爲了使綁定正常工作,在某些情況下,有必要添加綁定Mode = twoway和UpdateSourceTrigger = PropertyChanged – Cocomico 2016-01-26 14:29:04

0

我想你的解決方案,但它失敗了我。它並沒有將價值轉嫁給內部控制。我所做的是在外部的控制和約束同樣依賴屬性的聲明內而外這樣的:

// 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> 
相關問題