web-dev-qa-db-ja.com

WPFでバインドするための内部コントロールプロパティの公開

[編集]:私は自分でこれを行う方法を見つけました。他の人が数日間グーグルするのを救うことを期待して私のソリューションを投稿しました。WPFの達人である場合は、私のソリューションを見てくださいそして、これを行うためのより良い/よりエレガントな/より効率的な方法があるかどうかを知らせてください。特に、私が知らないことを知りたいと思っています...このソリューションはどのように私を混乱させますか?問題は、実際には、内部コントロールのプロパティを公開することになります。

問題:XMLファイルのWPFでデータバインドGUIを自動生成するコードを作成しています。ノードタイプなどを決定するのに役立つxsdファイルがあります。単純なKey/Value要素は簡単です。

この要素を解析すると:

<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と同じになります。

標準のユーザーコントロールがこれを処理できるかどうか、またはセレクターから継承する必要があるかどうかはわかりません。

コントロールの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を初めて使用するので、それが私がとらなければならない道であるなら、すてきで単純な例が本当に役に立ちます。

42
fbl

私は自分でこれをどのように行うかを考え出した。他の人が機能するソリューションを見ることができるように、ここに答えを投稿しています。おそらく、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はまったく興味深いものではありません。これがまさに私が欲しかったものです。すべての標準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();

    }
}
49
fbl

私はあなたの解決策を試しましたが、それは私にとって失敗します。値は内部コントロールにまったく渡されません。私がしたことは、外部コントロールで同じ依存関係プロパティを宣言し、そのように内部から外部にバインドしました:

    // 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>
1
Jakub Pawlinski