0

I apologize for the lengthy question, but I feel like it is necessary to include all of this information.

Until now, I've been using a possibly-unorthodox way of adding UserControls to my applications. Let's say I have a UserControl called Diagnostics that has a button, that when clicked, performs a function that is specific to the application that owns it. For example, if I drop Diagnostics into AppA, I want it to display "A", and if I drop it into AppB, I want AppB to define the behavior so it displays "B".

I typically implement this via a callback interface that is passed to the UserControl's constructor, which is pretty straightforward. Here's some sample "code" that probably won't compile, but is presented just to clarify what I've basically done before, and what I am trying to do:

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

The problem I had in the past was understanding how to declare a UserControl that takes a parameter in its constructor (i.e. doesn't have a default constructor) in XAML, and apparently you can't. I worked around this with a fairly-inelegant method -- I would give the main panel a name in XAML, and then from code-behind I would create Diagnostics, passing it the necessary callback, and then I would add Diagnostics to the panel's list of children. Gross and violates usage of MVVM, but it works.

This weekend, I decided to try to learn how to do it for a class and a TextBox, and it turns out that all I had to do was to create a DependencyProperty in my UserControl and use databinding. It looks something like this:

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}" />

So Foo is a property of MyApp, which is databound to the Something DependencyProperty of Diagnostics. When I click the button in the UserControl, the behavior is defined by ClassA. Much better, and works with MVVM!

What I'd like to do now is to go one step further and instead pass a callback interface to my UserControl so that it can get the states of my digital inputs and outputs. I'm looking for something like this:

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}" />

While my code (maybe not the exact code above, but my real project) does compile successfully, it appears that the PropertyMetadata passed to Register is at fault. I get an exception that says "Default value type does not match type of property 'DioCallbackInterface'."

Am I doing something really unorthodox, or is this approach to databinding interfaces actually possible? If not, what are the recommended ways of defining how a UserControl behaves based on the application it's being used in?

Dave
  • 14,618
  • 13
  • 91
  • 145

1 Answers1

3

The exception you have mentioned because of this:

new PropertyMetadata(0)

You have passed 0 (of type Int32) instead of the null or whatever you like for your interface: IDioCallback.

I cannot say that the way you select is wrong, but you should keep in mind that every user of your UserControl must implement that interface you have defined. If you have several properties that you would like to pass to the UserControl, you can basically discard them via DependencyProperty.

In your case you also would like to inject some logic to the UserControl Button. Let me suppose that this control has only one button. MVVM-way to handle Button.Click event is done via ICommand - you can declare the command property in your ViewModel and use it as data source for data binding in your UserControl as DependencyProperty, passing it properly to the Button.

Also you can have an agreement with all of your data context, and use special name for that property. For example:

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

Implement it for each data context you need, and use TheCommand property name inside your data template of your UserControl. In the code-behind you can create type validation of DataContext passed to your UserControl, and throw an exception in case the type is not implements your interface

Here several articles you probably should be interested in:

Using RelayCommand will simplify your life because you don't need to re-implement interface for every command, instead, you need to pass valid action that you want.

Community
  • 1
  • 1
stukselbax
  • 5,855
  • 3
  • 32
  • 54
  • Thanks for the reply! It would seem like what I want to do is not possible since I want to pass an interface. I've worked around it temporarily by passing in the derived class instead, but this isn't what I really want. I've been using RelayCommand for some years now and find it indispensible. My particular question isn't about the right way to handle commands, but how to get child controls a callback interface via XAML so that when buttons are clicked, they can perform a specific operation defined by the class that implements the interface. – Dave Dec 17 '14 at 20:13
  • ICommand is the interface. Can you pass an ICommand instance to the Button control? I think yes. So the same way you can pass instance of your interface to the UserControl. All you need to fix in your sample code is to use `new PropertyMetadata(null)`. – stukselbax Dec 18 '14 at 17:16
  • Thanks, I did use new PropertyMetadata(null) and that prevented the crash. The solution I went with in the end was to have references to my callback interfaces in the model that I ended up using for the DependencyProperty, and that seemed to work well enough. – Dave Dec 23 '14 at 14:28