2010-11-12 15 views
39

[Edytuj]: Pomyślałem, jak to zrobić na własną rękę. Wysłałem swoje rozwiązanie w nadziei, że uratuje ono kogoś innego przez kilka dni googlowania. Jeśli jesteś guru WPF, spójrz na moje rozwiązanie i daj mi znać, czy jest lepszy/bardziej elegancki/bardziej wydajny sposób, aby to zrobić. W szczególności jestem zainteresowany poznaniem tego, czego nie wiem ... jak to rozwiązanie doprowadzi mnie do szaleństwa? Problem naprawdę sprowadza się do ujawnienia właściwości kontroli wewnętrznej.Wystawienie właściwości kontroli wewnętrznej dla wiązania w WPF

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

Kiedy przeanalizować ten element:

<Key>value</Key> 

mogę utworzyć nowy 'KeyValueControl' i ustawić DataContext do tego elementu. KeyValue jest zdefiniowana jako UserControl i ma tylko kilka prostych powiązań. Działa doskonale dla każdego prostego XElement.

XAML wewnątrz tej kontroli wygląda następująco:

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

Rezultatem jest linia, która ma etykietę z nazwą elementu i pola tekstowego o wartości, które można edytować.

Są teraz czasy, w których muszę wyświetlać wartości wyszukiwania zamiast rzeczywistej wartości. Chciałbym utworzyć "KeyValueComboBox" podobny do powyższej KeyValueControl, ale być w stanie określić (w oparciu o informacje w pliku) ścieżki ItemsSource, Display i Value. Powiązania "Nazwa" i "Wartość" byłyby takie same jak KeyValueControl.

Nie wiem, czy standardowa kontrola użytkownika może sobie z tym poradzić, czy też muszę dziedziczyć z Selektora.

XAML w kontroli będzie wyglądać 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 kodu, chciałbym następnie zrobić coś takiego (zakładając, że ten węzeł jest „rzecz” i wymaga, aby wyświetlić listę rzeczy, tak więc użytkownik może 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 mogę odziedziczyć moja KeyValueComboBox z Control lub Selector?

2) Jeśli powinienem dziedziczyć po kontroli, w jaki sposób mogę odsłonić obiekt ItemsSource, DisplayMemberPath i ValueMemberPath w celu utworzenia powiązania?

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

Odpowiedz

47

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 pojawi się guru WPF i pokazać mi lepszy/bardziej elegancki sposób na zrobienie tego.

Tak więc odpowiedź zakończyła się na # 2. Ujawnienie wewnętrznych właściwości okazuje się być właściwą odpowiedzią. Ustawienie jest naprawdę łatwe ... kiedy już wiesz, jak to zrobić.Nie ma wielu pełnych przykładów tego (które mogłem znaleźć), więc mam nadzieję, że ten pomoże komuś, kto popadnie w ten problem.

ComboBoxWithLabel.xaml.cs

Ważną rzeczą w tym pliku jest wykorzystanie DependencyProperties. Zauważ, że wszystko, co teraz robimy, to tylko eksponowanie właściwości (LabelContent i ItemsSource). XAML zajmie się okablowaniem właściwości kontroli wewnętrznej tymi właściwościami zewnętrznymi.

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ść proste, z wyjątkiem powiązań na etykiecie i ComboBox ItemsSource. Odkryłem, że najłatwiejszym sposobem uzyskania tych powiązań jest zadeklarowanie właściwości w pliku .cs (jak wyżej), a następnie użycie projektanta VS2010 do skonfigurowania źródła powiązania z panelu właściwości. Zasadniczo jest to jedyny znany mi sposób powiązania właściwości wewnętrznej kontroli z kontrolą podstawową. Jeśli jest lepszy sposób, daj 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 użyć to nie jest ciekawe w ogóle .. co jest dokładnie to, co chciałem. Możesz ustawić 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> 

dla kompletności sake, tutaj jest 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

Używam podobną technikę, ale działa w kłopoty, kiedy mój same elementy są skonfigurowane do korzystania z powiązań RelativeSource. Tak więc w twoim przypadku, jeśli przypisałeś kolekcję elementów ComboBoxItem jako Tworzywo ItemsSource, a jeden ma powiązanie z czymś w drzewie logicznym, stosując wiązanie RelativeSource do FindAncestor w celu powiązania z właściwością zdefiniowaną w tym elemencie anscestor, to wiązanie się nie powiedzie. – jpierson

+0

Po prostu użyłem tego dla bardzo podobnej sprawy, dobrej odpowiedzi. W mojej aplikacji byłam wiążąca dla SelectedItem z Combobox. Należy zauważyć, że aby wiązanie działało poprawnie, w niektórych przypadkach konieczne jest dodanie trybu wiązania = twoway i UpdateSourceTrigger = PropertyChanged – Cocomico

0

próbowałem swoje rozwiązanie, ale to nie dla mnie. W ogóle nie przekazuje wartości do kontroli wewnętrznej. To, co zrobiłem jest deklaracja samych właściwościach uzależniających w kontroli zewnętrznej i wewnętrznej na zewnętrzną granicę tak:

// 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> 
Powiązane problemy