2010-11-12 59 views
39

[Chỉnh sửa]: Tôi đã tìm ra cách tự làm điều này. Tôi đã đăng giải pháp của mình với hy vọng rằng nó sẽ tiết kiệm cho người khác một vài ngày googling. Nếu bạn là một guru WPF, hãy nhìn vào giải pháp của tôi và cho tôi biết nếu có một cách tốt hơn/thanh lịch hơn/hiệu quả hơn để làm điều này. Đặc biệt, tôi quan tâm đến việc biết những gì tôi không biết ... làm thế nào là giải pháp này sẽ vít tôi xuống đường? Vấn đề thực sự sôi xuống để lộ các đặc tính điều khiển bên trong.Hiển thị các thuộc tính Điều khiển bên trong để gắn kết trong WPF

Sự cố: Tôi đang tạo một số mã để tự động tạo GUI kết nối dữ liệu trong WPF cho tệp XML. Tôi có một tập tin xsd có thể giúp tôi xác định các loại nút, vv Đơn giản Key/giá trị yếu tố rất dễ dàng.

Khi tôi phân tích yếu tố này:

<Key>value</Key> 

tôi có thể tạo ra một mới 'KeyValueControl' và thiết lập DataContext đến yếu tố này. KeyValue được định nghĩa là một UserControl và chỉ có một số ràng buộc đơn giản trên nó. Nó hoạt động tuyệt vời cho bất kỳ XElement đơn giản nào.

Các XAML bên trong kiểm soát này trông như thế này:

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

Kết quả là một dòng mà có một nhãn với tên phần tử và một hộp văn bản với giá trị mà tôi có thể chỉnh sửa.

Bây giờ, có những lúc tôi cần hiển thị giá trị tra cứu thay vì giá trị thực tế. Tôi muốn tạo một 'KeyValueComboBox' tương tự như KeyValueControl ở trên nhưng có thể chỉ định (dựa trên thông tin trong tệp) đường dẫn ItemsSource, Display và Value. Các ràng buộc 'Tên' và 'Giá trị' sẽ giống như KeyValueControl.

Tôi không biết liệu điều khiển người dùng chuẩn có thể xử lý điều này hay tôi cần kế thừa từ Bộ chọn.

Các XAML trong việc kiểm soát sẽ giống như thế này:

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

Trong mã của tôi, sau đó tôi sẽ làm một cái gì đó như thế này (giả định rằng nút này là một 'Thing' và cần phải hiển thị một danh sách các Mọi thứ vì vậy người dùng có thể chọn ID:

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

vì vậy, câu hỏi của tôi là:

1) tôi có nên kế thừa KeyValueComboBox tôi từ kiểm soát hoặc Selector?

2) Nếu tôi nên kế thừa từ Kiểm soát, làm cách nào để hiển thị các mục của Hộp kết hợp bên trong, DisplayMemberPath và ValueMemberPath để ràng buộc?

3) Nếu tôi cần kế thừa từ Bộ chọn, ai đó có thể cung cấp ví dụ nhỏ về cách tôi có thể bắt đầu với điều đó không? Một lần nữa, tôi mới vào WPF vì vậy một ví dụ đơn giản, tốt đẹp sẽ thực sự giúp đỡ nếu đó là con đường tôi cần phải thực hiện.

Trả lời

47

Tôi đã kết thúc tìm cách tự mình làm điều này. Tôi đang đăng câu trả lời ở đây để những người khác có thể nhìn thấy một giải pháp hoạt động, và có lẽ một guru WPF sẽ đến và cho tôi thấy một cách tốt hơn/thanh lịch hơn để làm điều này.

Vì vậy, câu trả lời đã kết thúc bằng # 2. Việc phơi bày các thuộc tính bên trong hóa ra là câu trả lời đúng. Thiết lập nó thực sự là khá dễ dàng .. một khi bạn biết làm thế nào để làm điều đó.Không có nhiều ví dụ hoàn chỉnh về điều này (mà tôi có thể tìm thấy), vì vậy hy vọng điều này sẽ giúp người khác gặp phải vấn đề này.

ComboBoxWithLabel.xaml.cs

Điều quan trọng trong tập tin này là việc sử dụng DependencyProperties. Lưu ý rằng tất cả những gì chúng tôi đang thực hiện ngay bây giờ là chỉ hiển thị các thuộc tính (LabelContent và ItemsSource). XAML sẽ chăm sóc dây các thuộc tính của bộ điều khiển bên trong với các đặc tính bên ngoài này.

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

Các XAML là khá đơn giản, với ngoại lệ của các ràng buộc trên nhãn và ComboBox ItemsSource. Tôi thấy rằng cách dễ nhất để có được các ràng buộc này là khai báo các thuộc tính trong tệp .cs (như trên) và sau đó sử dụng trình thiết kế VS2010 để thiết lập nguồn ràng buộc từ ngăn thuộc tính. Về cơ bản, đây là cách duy nhất tôi biết để liên kết các thuộc tính của một điều khiển bên trong với điều khiển cơ sở. Nếu có cách nào tốt hơn, hãy cho tôi biết.

<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

Các XAML để sử dụng này là không thú vị chút nào .. đó là chính xác những gì tôi muốn. Bạn có thể đặt ItemsSource và LabelContent qua tất cả các kỹ thuật WPF chuẩn.

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

cho đầy đủ Sake, đây là 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

Tôi đang sử dụng một kỹ thuật tương tự nhưng chạy vào rắc rối khi tôi bản thân các mục được thiết lập để sử dụng các liên kết RelativeSource. Vì vậy, trong trường hợp của bạn nếu bạn gán một tập hợp các phần tử ComboBoxItem như là ItemsSource của bạn và có một ràng buộc với một cái gì đó lên cây logic bằng cách sử dụng RelativeSource binding đến FindAncestor để liên kết với một thuộc tính được định nghĩa trên phần tử anscestor đó thì ràng buộc sẽ thất bại. – jpierson

+0

Tôi chỉ sử dụng điều này cho một trường hợp rất giống nhau, câu trả lời tốt. Trong ứng dụng của tôi, tôi đã liên kết với SelectedItem của một Combobox. Nó chỉ nên được lưu ý rằng để ràng buộc để hoạt động đúng, trong một số trường hợp nó là cần thiết để thêm vào chế độ ràng buộc = twoway và UpdateSourceTrigger = PropertyChanged – Cocomico

0

tôi đã cố gắng giải pháp của bạn, nhưng nó không cho tôi. Nó không truyền giá trị cho điều khiển bên trong.

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

Thần trong XAML:: khai tài sản phụ thuộc cùng kiểm soát bên ngoài và bị ràng buộc bên trong để bên ngoài như những gì tôi đã làm là

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

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

    </UserControl> 
Các vấn đề liên quan