Odsłanianie właściwości kontroli wewnętrznej dla wiązania w WPF

[Edit]: wymyśliłem, jak to zrobić na własną rękę. Opublikowałem moje rozwiązanie w nadziei, że uratuje to komuś kilka dni googlowania. Jeśli jesteś guru WPF, spójrz na moje rozwiązanie i daj mi znać, czy istnieje lepszy / bardziej elegancki/skuteczniejszy sposób na to. W szczególności interesuje mnie to, czego Nie wiem... jak to rozwiązanie ma mnie wprawić w zakłopotanie? Problem naprawdę sprowadza się do ujawnienia wewnętrznej kontroli właściwości.

Problem: Tworzę kod do automatycznego generowania GUI związanego z danymi w WPF dla pliku XML. Mam plik xsd, który może pomóc mi określić typy węzłów itp. Proste elementy klucza / wartości są łatwe.

Kiedy analizuję ten element:

<Key>value</Key>

Mogę utworzyć nowy 'KeyValueControl' i ustawić DataContext na ten element. KeyValue jest zdefiniowany jako UserControl i ma tylko kilka prostych powiązań. Działa świetnie dla każdego prostego XElement.

XAML wewnątrz tej kontrolki wygląda tak:

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

Wynikiem jest linia, która ma etykietę z nazwą elementu i pole tekstowe z wartością, którą mogę edytować.

Teraz, są chwile, kiedy muszę wyświetlić wartości lookup zamiast rzeczywistej wartości. Chciałbym utworzyć 'KeyValueComboBox' podobny do powyższego KeyValueControl ale być w stanie określić (na podstawie informacji w pliku) ItemsSource, Display i ścieżki wartości. Wiązania "Nazwa" i "wartość" będą takie same jak kluczowa Kontrola.

Nie wiem, czy standardowa kontrola użytkownika poradzi sobie z tym, czy muszę dziedziczyć z selektora.

XAML w sterowniku wyglądałby mniej więcej tak:

<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]/>

W moim kodzie, chciałbym zrobić coś takiego (zakładając, że ten węzeł jest 'rzeczą' I musi wyświetlić listę rzeczy, aby użytkownik mógł wybrać ID:

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

Więc moje pytania to:

1) Czy powinienem odziedziczyć KeyValueComboBox od Control lub Selektor?

2) Jeśli powinienem dziedziczyć z Control, w jaki sposób ujawnić wewnętrzne elementy skrzynki Combo ' S Source, DisplayMemberPath i ValueMemberPath do wiązania?

3) Jeśli muszę dziedziczyć z selektora, czy ktoś może podać mały przykład, Jak mogę zacząć z tym? Ponownie, jestem nowy w WPF, więc miły, prosty przykład naprawdę pomoże, jeśli to droga, którą muszę obrać.

Author: Dave Clemmer, 2010-11-13

2 answers

Skończyłem zastanawiając się, jak to zrobić na własną rękę. Zamieszczam tutaj odpowiedź, aby inni mogli zobaczyć rozwiązanie, które działa, a może przyjdzie guru WPF i pokaże mi lepszy / bardziej elegancki sposób na to.

Więc odpowiedź skończyła się na # 2. Ujawnienie wewnętrznych właściwości okazuje się być właściwą odpowiedzią. Skonfigurowanie go jest całkiem łatwe.. kiedy już wiesz, jak to zrobić. Nie ma wielu kompletnych przykładów tego (które mógłbym znaleźć), więc mam nadzieję, że ten pomoże kogoś innego, kto napotkał ten problem.

ComboBoxWithLabel.xaml.cs

Ważną rzeczą w tym pliku jest użycie DependencyProperties. Zauważ, że jedyne, co teraz robimy, to ujawnianie właściwości (LabelContent i ItemsSource). XAML zajmie się okablowaniem właściwości kontroli wewnętrznej do tych właściwości zewnętrznych.

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 jest dość prosty, z wyjątkiem wiązania na etykiecie i elementach Comboboxu. Odkryłem, że najprostszym sposobem na poprawienie tych wiązań jest zadeklarowanie właściwości wplik cs (jak wyżej), a następnie użyj VS2010 designer, aby ustawić źródło powiązania z panelu właściwości. Zasadniczo, jest to jedyny sposób, jaki znam, aby powiązać właściwości kontroli wewnętrznej z kontrolą bazową. Jeśli jest lepszy sposób, proszę dać mi znać.

<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 aby tego użyć nie jest interesujące w ogóle.. tego właśnie chciałem. Możesz ustawić źródła ItemsSource i LabelContent za pomocą wszystkich standardowych technik 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>

Ze Względu Na kompletność, oto główne okno.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();

    }
}
 48
Author: fbl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-14 05:14:23

Próbowałem Twojego rozwiązania, ale mi się nie udało. W ogóle nie przekazuje wartości kontroli wewnętrznej. To, co zrobiłem, to deklaracja tych samych właściwości zależności w zewnętrznej kontroli i związana wewnętrzna z zewnętrzną w ten sposób:

    // 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); }
    }

Niż w xaml:

  <UserControl x:Class="CBRControls.TimePickerEx" x:Name="TimePickerExControl"
        ...
        >

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

  </UserControl>
 1
Author: Jakub Pawlinski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-03 11:13:15