Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reuse a WPF user control but change a bound property?

I have a WPF user control which uses a property from my viewmodel for binding. I now want to use two instances of that user control with the same viewmodel, but override the binding in one of them.

The code looks like this

User control

<UserControl x:Class="TestWpfApp.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TestWpfApp">
    <Grid>
        <DockPanel>
            <Label Margin="10" Content="{Binding MyLabel}"/>
            <Button Margin="10" Content="{Binding MyButton}"/>
        </DockPanel>
    </Grid>
</UserControl>

Main view

<Window x:Class="TestWpfApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestWpfApp"
        Title="MainWindow" Height="150" Width="525">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <DockPanel>
            <local:UserControl1 DockPanel.Dock="Top" x:Name="UserControl1"/>
            <!--In this instance of UserControl1, I want the Label to bind to the MyNewLabel-->
            <local:UserControl1 DockPanel.Dock="Top" x:Name="UserControl2"/>
        </DockPanel>
    </Grid>
</Window>

Viewmodel

public class ViewModel
{
    public string MyLabel => "Label1";
    public string MyButton => "Button1";
    public string MyNewLabel => "Label2";
}

In the second instance of UserControl1, I would like the label to be bound to a different property. I tried setting that property as a resource on the user control, and then overriding it in the main view but I wasn't able to get that to work.

In reality, I'm using DevExpress controls and POCO viewmodels, if that makes things easier.

like image 280
Kris Harper Avatar asked Oct 08 '17 14:10

Kris Harper


1 Answers

When creating custom control - it's not a good idea to bind controls inside that custom control to arbitrary properties provided by external data context. Not only you are getting problems like you have now, user of that control has no idea which datacontext with which properties should he provide for that control to work. Only looking at source code of your UserControl one might find that it expects data context with properties MyLabel and MyButton. Instead - make control self-contained by introducing dependency properties on control itself:

public partial class UserControl1 : UserControl {
    public UserControl1() {
        InitializeComponent();
    }

    public string Text
    {
        get { return (string) GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new PropertyMetadata(null));

    public string ButtonText
    {
        get { return (string) GetValue(ButtonTextProperty); }
        set { SetValue(ButtonTextProperty, value); }
    }

    public static readonly DependencyProperty ButtonTextProperty =
        DependencyProperty.Register("ButtonText", typeof(string), typeof(UserControl1), new PropertyMetadata(null));
}

And bind to those properties:

<UserControl x:Class="WpfApplication1.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"                          
             x:Name="self">
    <Grid>
        <DockPanel>
            <Label Margin="10"
                   Content="{Binding Text, ElementName=self}" />
            <Button Margin="10"
                    Content="{Binding ButtonText, ElementName=self}" />
        </DockPanel>
    </Grid>
</UserControl>

Then in main window - bind those dependency properties to your model:

<Window.DataContext>
    <my:ViewModel />
</Window.DataContext>
<Grid>
    <DockPanel>
        <my:UserControl1 DockPanel.Dock="Top"
                            x:Name="UserControl1" Text="{Binding MyLabel}" ButtonText="{Binding MyButton}" />
        <!--In this instance of UserControl1, I want the Label to bind to the MyNewLabel-->
        <my:UserControl1 DockPanel.Dock="Top"
                         x:Name="UserControl2"
                         Text="{Binding MyNewLabel}"
                         ButtonText="{Binding MyButton}" />
    </DockPanel>
</Grid>
like image 139
Evk Avatar answered Sep 20 '22 16:09

Evk