Предоставление внутренних свойств Control для связывания в WPF - PullRequest
40 голосов
/ 13 ноября 2010

[Редактировать]: Я понял, как это сделать самостоятельно.Я разместил свое решение в надежде, что оно сэкономит кому-то еще несколько дней поиска в Google.Если вы гуру WPF, пожалуйста, посмотрите на мое решение и дайте мне знать, если есть лучший / более элегантный / более эффективный способ сделать это.В частности, мне интересно знать, чего я не знаю ... как это решение приведет меня в тупик?Проблема действительно сводится к раскрытию свойств внутреннего элемента управления.

Проблема: я создаю некоторый код для автоматической генерации привязанного к данным графического интерфейса пользователя в WPF для файла XML.У меня есть файл xsd, который может помочь мне определить типы узлов и т. Д. Простые элементы Key / Value просты.

Когда я анализирую этот элемент:

<Key>value</Key>

, я могу создать новый'KeyValueControl' и установите DataContext для этого элемента.KeyValue определяется как UserControl и содержит только несколько простых привязок.Он прекрасно работает для любого простого XElement.

XAML внутри этого элемента управления выглядит следующим образом:

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

В результате получается строка с меткой с именем элемента и текстовым полем сзначение, которое я могу редактировать.

Теперь бывают случаи, когда мне нужно отображать значения поиска вместо фактического значения.Я хотел бы создать KeyValueComboBox, аналогичный описанному выше KeyValueControl, но иметь возможность указать (на основе информации в файле) пути 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]/>

В моем коде я бы затем сделал что-то вроде этого (предполагая, что этот узел является «вещью» и ему нужноотобразить список вещей, чтобы пользователь мог выбрать идентификатор:

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, как я могу предоставить внутренние элементы Combo Box ItemsSource, DisplayMemberPath и ValueMemberPath для привязки?

3) Если мне нужно унаследовать от Selector, может ли кто-нибудь предоставить небольшой примеркак я могу начать с этим?Опять же, я новичок в WPF, поэтому хороший, простой пример действительно помог бы, если мне нужно идти по этому пути.

Ответы [ 2 ]

48 голосов
/ 14 ноября 2010

В итоге я решил, как это сделать самостоятельно.Я публикую ответ здесь, чтобы другие могли увидеть работающее решение, и, возможно, придет гуру 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 довольно прост, за исключениемпривязки к метке и источнику 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();

    }
}
1 голос
/ 03 декабря 2015

Я попробовал ваше решение, но оно мне не удалось. Он вообще не передает значение внутреннему контролю. Что я сделал, так это объявил те же свойства зависимостей во внешнем элементе управления и привязал их к внешнему элементу следующим образом:

    // 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...