2014-12-15 1 views
0

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

До сих пор я использовал возможно-неортодоксальный способ добавления UserControls в свои приложения. Предположим, что у меня есть UserControl с именем Diagnostics, у которого есть кнопка, которая при щелчке выполняет функцию, специфичную для приложения, которое ее владеет. Например, если я отменил Диагностику в AppA, я хочу, чтобы она отображала «A», и если я отброшу ее в AppB, я хочу, чтобы AppB определял поведение, чтобы он отображал «B».

Обычно я реализую это через интерфейс обратного вызова, который передается конструктору UserControl, что довольно просто. Вот некоторые примеры «код», который, вероятно, не будет компилироваться, но представлена ​​только уточнить, что я в основном делали раньше, и то, что я пытаюсь сделать:

public interface IDiagnosticsCallback { 
    void DisplayDiagnostics(); // implemented by owner of Diagnostics UserControl 
} 

public class MyApp : IDiagnosticsCallback { 
    public void DisplayDiagnostics() { 
     MessageBox.Show("Diagnostics displayed specifically for MyApp here"); 
    } 
} 

public Diagnostics : UserControl { 
    private IDiagnosticsCallback _callback { get; private set; } 

    public Diagnostics(IDiagnosticsCallback callback) { 
     _callback = callback; 
    } 

    public void ShowDiagnostics_Click(object sender, EventArgs e) { 
     _callback.DisplayDiagnostics(); 
    } 
} 

проблема, которую я имел в прошлом был понимание того, как объявить UserControl, который принимает параметр в своем конструкторе (т. е. не имеет конструктора по умолчанию) в XAML, и, по-видимому, вы не можете. Я работал над этим с довольно-неэлегантным методом - я бы дал главной панели имя в XAML, а затем из кода-кода я бы создал Diagnostics, передав ему необходимый обратный вызов, а затем добавлю диагностику в список панели детей. Гросс и нарушает использование MVVM, но он работает.

В этот уик-энд я решил попробовать узнать, как это сделать для класса и TextBox, и оказалось, что все, что мне нужно было сделать, это создать DependencyProperty в моем UserControl и использовать привязку данных. Это выглядит что-то вроде этого:

public ClassA 
{ 
    public void ShowSomethingSpecial() 
    { 
     MessageBox.Show("Watch me dance!"); 
    } 
} 

public MyApp 
{ 
    public ClassA Foo { get; set; } 
} 

public Diagnostics : UserControl 
{ 
    public static readonly DependencyProperty SomethingProperty = DependencyProperty.Register("Something", typeof(ClassA), typeof(Diagnostics), new PropertyMetadata()); 

    public ClassA Something 
    { 
     get { return (MyApp)GetValue(SomethingProperty); } 
     set { SetValue(SomethingProperty, value); } 
    } 

    // now uses default constructor 

    public void ShowSomethingSpecial_Click(object sender, EventArgs e) 
    { 
     Something.ShowSomethingSpecial(); 
    } 
} 


MyApp.xaml 
<diags:Diagnostics Something="{Binding Foo}" /> 

Так Foo является собственностью MyApp, которая DataBound на что-то DependencyProperty Диагностики. Когда я нажимаю кнопку в UserControl, поведение определяется ClassA. Гораздо лучше, и работает с MVVM!

Теперь я хотел бы сделать еще один шаг и вместо этого передать интерфейс обратного вызова в свой UserControl, чтобы он мог получать состояния моих цифровых входов и выходов. Я ищу что-то вроде этого:

public Diagnostics : UserControl 
{ 
    public interface IDioCallback 
    { 
     short ReadInputs(); 
     short ReadOutputs(); 
     void SetOutput(char bit); 
    } 

    public IDioCallback DioCallbackInterface { 
     get { return (IDioCallback)GetValue(DioCallbackInterfaceProperty); } 
     set { SetValue(DioCallbackInterfaceProperty,value); } 
    } 

    // Using a DependencyProperty as the backing store for DioCallbackInterface. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty DioCallbackInterfaceProperty = DependencyProperty.Register("DioCallbackInterface",typeof(IDioCallback),typeof(Diagnostics),new PropertyMetadata(0)); // PropertyMetadata is the problem... 
} 

public class DIO : IDioCallback 
{ 
    public short ReadInputs() { return 0; } 
    public short ReadOutputs() { return 0; } 
    public void SetOutput(char bit) {} 
} 

public class MyApp 
{ 
    public DIO MyDIO { get; set; } 
} 

MyApp.xaml 
<diags:Diagnostics DioCallbackInterface="{Binding MyDIO}" /> 

Хотя мой код (возможно не точный код выше, но мой реальный проект) компилируется успешно, оказывается, что PropertyMetadata передается Регистрация виноват. Я получаю исключение, которое говорит: «Тип значения по умолчанию не соответствует типу свойства« DioCallbackInterface ».»

Я делаю что-то действительно неортодоксальное, или этот подход к интерфейсам привязки данных действительно возможен? Если нет, то какие рекомендуемые способы определения поведения UserControl на основе приложения, в котором он используется?

ответ

2

Исключение вы упомянули из-за этого:

new PropertyMetadata(0) 

Вы прошли 0 (типа Int32) вместо нулевой или что угодно для вашего интерфейса: IDioCallback.

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

В вашем случае вы также хотели бы ввести некоторую логику в UserControlКнопка. Позвольте мне предположить, что этот элемент управления имеет только одну кнопку. MVVM -way для обработки Button.Click события осуществляется через ICommand - вы можете объявить свойство команды в вашем ViewModel и использовать его в качестве источника данных для связывания данных в вашем UserControl в DependencyProperty, передавая его правильно к Баттону.

Также вы можете заключить соглашение со всем своим контекстом данных и использовать специальное имя для этого свойства. Например:

public interface IViewModelWithCommand 
{ 
    public ICommand TheCommand { get; } 
} 

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

Вот несколько статей, которые вы, вероятно, должны быть заинтересованы в:

Использование RelayCommand упростит вашу жизнь, потому что вам не нужно повторно реализовывать интерфейс для каждой команды, вместо этого вам необходимо передать действительное действие, которое вы хотите.

+0

Спасибо за ответ! Казалось бы, что я хочу сделать, это невозможно, так как я хочу передать интерфейс. Я работал над этим временно, перейдя в производный класс, но это не то, что я действительно хочу. Я использую RelayCommand уже несколько лет и считаю это незаменимым. Мой конкретный вопрос заключается не в правильном способе обработки команд, а в том, как получить дочерние элементы управления интерфейсом обратного вызова через XAML, чтобы при нажатии кнопок они могли выполнять определенную операцию, определенную классом, реализующим интерфейс. – Dave

+0

ICommand - это интерфейс. Можете ли вы передать экземпляр ICommand на элемент управления Button? Я думаю да. Таким же образом вы можете передать экземпляр вашего интерфейса в UserControl. Все, что вам нужно исправить в вашем примере кода, это использовать 'new PropertyMetadata (null)'. – stukselbax

+0

Спасибо, я использовал новый PropertyMetadata (null) и предотвратил сбой. Решение, с которым я пошел, в конце концов состояло в том, чтобы иметь ссылки на мои интерфейсы обратного вызова в модели, которые я использовал для DependencyProperty, и это, казалось, работало достаточно хорошо. – Dave

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