2016-12-05 5 views
0

У меня довольно простое требование, когда UserControl должен предлагать пользователю способ выбора элемента из его дропплитора. Когда пользователь нажимает кнопку, UserControl выполнит некоторое количество внутренних тестов, тогда он вызовет метод в главном приложении и передаст ему выбор пользователя.Понимание того, как работает привязка данных между свойством DependencyProperty UserControl и его назначением хост-приложения

У меня это работает с использованием MVVM, но я немного озадачен тем, что должен был сделать, чтобы он работал. По моему опыту привязки данных кажется, что у меня все еще есть некоторые пробелы в моих знаниях, потому что каждая новая реализация, похоже, вызывает у меня проблемы, которых я не ожидал.

Моих UserControl просто, это выпадающий список и кнопка:

<UserControl x:Class="MyUserControl.UserControl1" 
      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="300" d:DesignWidth="300"> 
    <StackPanel Orientation="Vertical"> 
     <ComboBox ItemsSource="{Binding MyItems}" IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding MySelectedItem}" /> 
     <Button Content="Click me" Command="{Binding ClickMeCommand}" /> 
    </StackPanel> 
</UserControl> 

код позади выглядит это и просто устанавливает данные для управления:

using GalaSoft.MvvmLight.CommandWpf; 
using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
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; 

namespace MyUserControl 
{ 
    /// <summary> 
    /// Interaction logic for UserControl1.xaml 
    /// </summary> 
    public partial class UserControl1 : UserControl 
    { 
     public ICollectionView MyItems { get; set; } 
     public RelayCommand ClickMeCommand { get; set; } 
     public string MySelectedItem { get; set; } 

     public ICommand HostClickMeCommand 
     { 
      get { return (ICommand)GetValue(HostClickMeCommandProperty); } 
      set { SetValue(HostClickMeCommandProperty, value); } 
     } 

     // Using a DependencyProperty as the backing store for HostClickMeCommand. This enables animation, styling, binding, etc... 
     public static readonly DependencyProperty HostClickMeCommandProperty = 
      DependencyProperty.Register("HostClickMeCommand", typeof(ICommand), typeof(UserControl1), new PropertyMetadata(null)); 

     public UserControl1() 
     { 
      InitializeComponent(); 
      DataContext = this; 
      MyItems = CollectionViewSource.GetDefaultView(new List<string> { "John", "Mary", "Joe" }); 
      ClickMeCommand = new RelayCommand(ExecuteClickMeCommand); 
     } 

     private void ExecuteClickMeCommand() 
     { 
      MessageBox.Show("Hello from user control!"); 
      if(HostClickMeCommand != null) { 
       HostClickMeCommand.Execute(MySelectedItem); 
      } 
     } 
    } 
} 

Вы заметите что обработчик кнопки для моего UserControl отобразит сообщение, а затем вызовет в мое приложение.

XAML приложения также очень легко:

<Window x:Class="MyHostApplication.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:uc="clr-namespace:MyUserControl;assembly=MyUserControl" 
     Title="MainWindow" Height="350" Width="525"> 
    <DockPanel> 
     <uc:UserControl1 HostClickMeCommand="{Binding MyHostClickMeCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" /> 
    </DockPanel> 
</Window> 

Как его фоновым кодом:

using GalaSoft.MvvmLight.CommandWpf; 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
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; 

namespace MyHostApplication { 
    /// <summary> 
    /// Interaction logic for MainWindow.xaml 
    /// </summary> 
    public partial class MainWindow : Window 
    { 
     public RelayCommand<string> MyHostClickMeCommand { get; set; } 

     public MainWindow() 
     { 
      InitializeComponent(); 
      DataContext = this; 
      MyHostClickMeCommand = new RelayCommand<string>((name) => { MessageBox.Show(String.Format("Hello from host, {0}!", name)); }); 
     } 
    } 
} 

Этот код работает отлично.

Но мой вопрос: Почему у меня должен быть RelativeSource, указанный в моей привязке? Так как DataContext для окна приложения сам по себе, почему окно не свяжет свойство зависимостей UserControl с MyHostClickMeCommand? Если я удалю RelativeSource, обработчик приложения не будет вызван.

Я также должен добавить, что причина, по которой я хочу выяснить, как правильно определить привязку, заключается в том, что я хочу иметь возможность устанавливать ViewModel моего приложения в другой класс. В идеале я хотел бы мое приложение, чтобы иметь это в XAML:

<Window.DataContext> 
    <local:MainViewModel /> 
</Window.DataContext> 

где MainViewModel находится в папке ViewModels в файле проекта моего приложения.

+1

Явная установка его DataContext, вероятно, является наиболее часто ошибочной при создании UserControl. Независимо от того, что они говорят вам в блогах или онлайн-учебниках, вы никогда не должны этого делать, потому что это эффективно предотвращает наследование вашего DataContext UserControl из его родительского элемента управления. – Clemens

+0

@ Clemens спасибо - да, когда я узнал о databinding около 7 лет назад, все это рекомендовали.И, к сожалению, я никогда не менялся со временем. Для меня это будет по-другому! – Dave

ответ

1

Всякий раз, когда я вижу это вне основного запуска приложения:

DataContext = this; 

или это

<UserControl.DataContext> 
    <local:MyViewModel /> 
</UserControl.DataContext> 

всегда подбрасывает красные флаги для меня.

Если это не специальный случай, всегда мой совет никогда hardcode DataContext свойство внутри UserControl. Поступая таким образом, вы предотвращаете передачу любого другого DataContext в UserControl, который поражает одно из главных преимуществ WPF в отношении наличия отдельного интерфейса и слоев данных.

Либо строить UserControl специально для использования с конкретной моделью или ViewModel, используемой в качестве DataContext, таких, как это:

<!-- Draw anything of type MyViewModel with control MyUserControl--> 
<!-- DataContext will automatically set to the MyViewModel --> 
<DataTemplate DataType="{x:Type local:MyViewModel}}"> 
    <local:MyUserControl /> 
</DataTemplate> 

Или строить с расчетом, что DataContext может быть абсолютно ничего, и DependencyProperites будет использоваться, чтобы придать элемент управления данными ему необходимо:

<!-- DataContext property can be anything, as long as it has a property called MyDataProperty --> 
<local:MyUserControl MyDependencyProperty="{Binding MyDataProperty}" /> 

Но чтобы ответить у наш вопрос, причина, по которой вам нужно использовать RelativeSource в вашей привязке, заключается в том, что привязки по умолчанию будут использовать DataContext, поэтому он пытается привязать к UserControl1.DataContext.MyHostClickMeCommand. Поскольку у вас есть hardcoded DataContext = this; в конструкторе, он пытается связать с MyUserControl1.MyHostClickMeCommand, которого не существует. Использование RelativeSource сообщает привязке, что оно должно получить источник от чего-то другого, кроме текущего DataContext.

Я вижу много путаницы о DataContext от WPF начинающих, и я обычно посылаю их к т his StackOverflow Answer about the DataContext is for

+0

Спасибо, @Rachel! Боковой вопрос об использовании - о чем в других проектах, которые могут использовать ViewModelLocator MvvmLightToolkit? Это кажется разумным способом указать DataContext (но в моем случае это тоже не сработало). Спасибо за другую ссылку. Я уверен, что прочитал это раньше, когда был новичком WPF. Я на самом деле думаю, что я все еще один! – Dave

+0

@Dave Я думал, что узнал ваше имя/значок :) Лично мне не нравится использовать «ViewModelLocator», поскольку я думаю, что он перемещает управление вашим приложением из ViewModels и в слой «Вид», где я не думаю, что это принадлежит. Если мне нужны данные времени разработки, я обычно просто использую контекст данных времени разработки с помощью «xmlns: d =» http://schemas.microsoft.com/expression/blend/2008 »и« d: DataContext ». Поэтому я не уверен, что смогу помочь вам с любыми вопросами, которые могут возникнуть в связи с их использованием. – Rachel

+0

Из моего понимания из [этого ответа] (http://stackoverflow.com/a/5462324/302677), когда я давно смотрел ViewModels, они в основном используются для предоставления данных времени разработки или подключения основных разделы вашего приложения, такие как подключение ApplicationView/ViewModel, DialogView/ViewModel, MenuView/ViewModel и т. д. Мои основные жалобы на это можно найти здесь (http://stackoverflow.com/a/6696983/302677) .. Я всегда хотел расширить этот ответ в сообщении в блоге о том, почему мне не нравятся ViewModelLocators, но я так и не добрался до него :) – Rachel

1

При создании привязки как следующий

<TextBox x:Name="foo" Text="{Binding MuhText}" /> 

это эквивалент следующие

foo.Text = foo.DataContext.MuhText; 

Пути привязки коренится в DataContext связанного элемента управления. Когда вы это говорите,

<UserControl x:Class="MyUserControl.UserControl1" 
    RemoveUselessNoise="true" > 
    <StackPanel Orientation="Vertical"> 
     <ComboBox ItemsSource="{Binding MyItems}" 
        SelectedItem="{Binding MySelectedItem}" /> 
     <Button Content="Click me" Command="{Binding ClickMeCommand}" /> 
    </StackPanel> 
</UserControl> 

Вы по-прежнему привязываетесь к DataContext элемента управления (ComboBox и Button).

Что вы хотите связать с экземпляром UserControl, в котором эти элементы управления определены. Некоторые люди предлагают вам сделать что-то вроде этого:

public UserControl1() 
{ 
    InitializeComponent(); 
    DataContext = this; 
} 

Эти люди сумасшедшие писаки, которые существуют только, чтобы установить вас на неудачу вниз по дороге. Обычно это приводит к тому, что привязки не работают должным образом и обычно прерывают поток DataContext.

Вы делаете почти все правильно. Ваше решение должно удалить это DataContext=this;, а затем переустановить привязку к вашему UserControl. Вы можете сделать это несколькими способами, но я думаю, что проще всего дать вашему корню x:Name и использовать привязку ElementName.

<UserControl x:Class="MyUserControl.UserControl1" 
      x:Name="RootNodeLol" 
      RemoveUselessNoise="true" > 
    <StackPanel Orientation="Vertical"> 
     <ComboBox ItemsSource="{Binding MyItems, ElementName=RootNodeLol}" 
        SelectedItem="{Binding MySelectedItem, ElementName=RootNodeLol}" /> 
     <Button Content="Click me" Command="{Binding ClickMeCommand, 
                 ElementName=RootNodeLol}" /> 
    </StackPanel> 
</UserControl> 

Предполагая, что здесь нет другого hanky panky, вы должны быть добры, чтобы пойти с этим.

Сторона примечания, вы должны получить копию Snoop. Вы можете проверить свои привязки во время выполнения и посмотреть, почему все работает не так, как ожидалось.

+0

Благодарим вас за комментарий. Я должен сказать, что я действительно смутился, когда вы сказали «сумасшедшие хаки», потому что я в основном использовал «DataContext = this» во всем моем программном обеспечении. В основном потому, что я не знал ничего лучшего, когда впервые узнал о MVVM и WPF, а также потому, что @Rachel довольно популярен в том, что UserControls, который я пишу, не для кого-то другого, а для моего. У меня еще есть чему поучиться! – Dave

+0

Я использовал Snoop для просмотра общей структуры раньше, но у меня не было успеха, анализируя связанные с ним проблемы с привязкой. Я попробую еще раз. Я удалил оскорбительные назначения DataContext и добавил имя в 'UserControl1' и' MyHostApplication', но в обоих случаях привязки больше не работают. Пульт droplist пуст, и обработчик команд не выполняется, но я также не получаю никаких ошибок привязки в окне вывода в VS2013 ... Snoop ничего не показывает, когда я выбираю «Показывать только визуальные эффекты с ошибками привязки». – Dave

+0

@Dave Вам может потребоваться указать режим привязки - TwoWay в UserControl. – Will

 Смежные вопросы

  • Нет связанных вопросов^_^