Put UserControl withing Dockpane

1580
3
Jump to solution
01-10-2019 07:14 AM
ModyBuchbinder
Esri Regular Contributor

Hello all

I have a few dockpanes that should share the same status ruler within then (show the step we are in).

The best way to do it is to create user control and use it in all dock pane and set the status within it in the code.

I created a WPF user control but I cannot find the way to put it in a few different dockpanes.

Every dock pane is a user control too...

I found the "ReusableUserControl" (under Framework) example but I am not clear how to apply it in my case.

Does anybody have a simple explanation how to do it?

Thanks

1 Solution

Accepted Solutions
CharlesMacleod
Esri Regular Contributor

Mody, to answer the question of how to use a user control "within" a user control I recommend doing a google on "WPF DataTemplate" and "WPF ContentPresenter" and going on stackoverflow- You will find lots of examples there. To help you get going here are three options I put together but there many different permutations and possibilities in WPF...you will need to evaluate which is best for your situation.

Alright, so, that said, assume this is your control (xaml not shown)

public partial class ModyView : UserControl {

Option 1. Simply add a reference to it in your dockpane. This is the easiest:

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) reference your user control -->
     <local:ModyView x:Name="ModyViewControl" Margin="3,4,3,4" />‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Option 2: Use something called a content presenter. A content presenter is basically a placeholder into which you can put any content - in this case your user control. Bind it to a property of type UserControl or FrameworkElement (or even "ModyView" - our specific custom UserControl type if you prefer)...

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) add a content presenter -->
     <ContentPresenter Content="{Binding UserControlContent, Mode=OneWay}" />

and in the dockpane view model....when we set the property, the content will be shown.

internal class TheDockPaneViewModel : DockPane‍ {

  //the property in the binding - can also use FrameworkElement as the type
  private UserControl _content = null;

  public UserControl UserControlContent {
         get { return _content; }
         internal set { SetProperty(ref _content, value, () => UserControlContent); }
  }


  //usage...set the property during runtime...
  _myDockPane.UserControlContent = new ModyView();

Option 3 Assume you have a non-visual class that you want to set as the content and you will be using your user control to render it. For example, a non-visual class called "StatusRuler"...

public class StatusRuler {
   public string Title { ...
   public int Step { ....
   public bool Completed { ...
   ‍‍‍‍‍

The dockpane has a content presenter the same as option 2 but we now bind it to a property of type of "StatusRuler" (see the bottom code window)....the non-visual class....rather than a user control directly. This is our updated dockpane.

internal class TheDockPaneViewModel : DockPane‍ {

  //the property in the binding - set to a non-visual type
  private StatusRuler _ruler = null;

  public StatusRuler StatusRuler {
         get { return _ruler; }
         internal set { SetProperty(ref _ruler, value, () => StatusRuler); }
  }


  //usage...set the property during runtime...
  _myDockPane.StatusRuler = new StatusRuler();‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Next we add a data template to the dockpane view - a data template links or associates our user control "ModyView" with the non-visual type "StatusRuler". Usually, the data template goes in your dockpane resources but it can go in a separate resource dictionary or even the resources of the ContentPresenter itself. It just depends on whether the data template is something you want to re-use on multiple dockpanes (without copy/pasting) or not. When you set the ContentPresenter to be an instance of the non-visual type "StatusRuler", the data template kicks in to tell the ContentPresenter to use an instance of the user control "ModyView" to show it. Et voila.

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">
<UserControl.Resources>
  <ResourceDictionary>
    <DataTemplate DataType="{x:Type local:StatusRuler}">
         <local:ModyView />
    </DataTemplate>
...

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) add a content presenter -->
     <ContentPresenter Content="{Binding StatusRuler, Mode=OneWay}" /> 

View solution in original post

3 Replies
CharlesMacleod
Esri Regular Contributor

Mody, to answer the question of how to use a user control "within" a user control I recommend doing a google on "WPF DataTemplate" and "WPF ContentPresenter" and going on stackoverflow- You will find lots of examples there. To help you get going here are three options I put together but there many different permutations and possibilities in WPF...you will need to evaluate which is best for your situation.

Alright, so, that said, assume this is your control (xaml not shown)

public partial class ModyView : UserControl {

Option 1. Simply add a reference to it in your dockpane. This is the easiest:

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) reference your user control -->
     <local:ModyView x:Name="ModyViewControl" Margin="3,4,3,4" />‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Option 2: Use something called a content presenter. A content presenter is basically a placeholder into which you can put any content - in this case your user control. Bind it to a property of type UserControl or FrameworkElement (or even "ModyView" - our specific custom UserControl type if you prefer)...

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) add a content presenter -->
     <ContentPresenter Content="{Binding UserControlContent, Mode=OneWay}" />

and in the dockpane view model....when we set the property, the content will be shown.

internal class TheDockPaneViewModel : DockPane‍ {

  //the property in the binding - can also use FrameworkElement as the type
  private UserControl _content = null;

  public UserControl UserControlContent {
         get { return _content; }
         internal set { SetProperty(ref _content, value, () => UserControlContent); }
  }


  //usage...set the property during runtime...
  _myDockPane.UserControlContent = new ModyView();

Option 3 Assume you have a non-visual class that you want to set as the content and you will be using your user control to render it. For example, a non-visual class called "StatusRuler"...

public class StatusRuler {
   public string Title { ...
   public int Step { ....
   public bool Completed { ...
   ‍‍‍‍‍

The dockpane has a content presenter the same as option 2 but we now bind it to a property of type of "StatusRuler" (see the bottom code window)....the non-visual class....rather than a user control directly. This is our updated dockpane.

internal class TheDockPaneViewModel : DockPane‍ {

  //the property in the binding - set to a non-visual type
  private StatusRuler _ruler = null;

  public StatusRuler StatusRuler {
         get { return _ruler; }
         internal set { SetProperty(ref _ruler, value, () => StatusRuler); }
  }


  //usage...set the property during runtime...
  _myDockPane.StatusRuler = new StatusRuler();‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

Next we add a data template to the dockpane view - a data template links or associates our user control "ModyView" with the non-visual type "StatusRuler". Usually, the data template goes in your dockpane resources but it can go in a separate resource dictionary or even the resources of the ContentPresenter itself. It just depends on whether the data template is something you want to re-use on multiple dockpanes (without copy/pasting) or not. When you set the ContentPresenter to be an instance of the non-visual type "StatusRuler", the data template kicks in to tell the ContentPresenter to use an instance of the user control "ModyView" to show it. Et voila.

<!-- this is your dockpane -->
<UserControl x:Class="ModyAddin.TheDockpaneView"
             ...
             xmlns:local="clr-namespace:ModyAddin">
<UserControl.Resources>
  <ResourceDictionary>
    <DataTemplate DataType="{x:Type local:StatusRuler}">
         <local:ModyView />
    </DataTemplate>
...

<Grid>
   <Border Background="..... >
     <!-- In the dockpane xaml (i.e. the view) add a content presenter -->
     <ContentPresenter Content="{Binding StatusRuler, Mode=OneWay}" /> 
ModyBuchbinder
Esri Regular Contributor

Hi Charlie

Great answer as usual. I used option 1 (in my original code I missed the xmlns:local)

Now I need to control some of the user control content. For example my step ruler include a nice icon for each step.

The user control have property "setStep" and the code inside highlight the correct icon. 

Is it best practice to create a ViewModel for the user control or just use the code behind?

Then do I have to move to option 2, get the userControlContent as a class and set the setStep for it?

Many thanks

Mody

0 Kudos
CharlesMacleod
Esri Regular Contributor

This is a great question. Generally speaking, if you are developing a re-usable User Control then the properties should go on the UserControl itself and, if needed, have it bind its DataContext to itself rather than using a separate (non-visual) view model.

If you do need to bind the user control to itself you must be careful NOT to break the binding "chain" or hierarchy....I know this sounds a little convoluted but their are some great articles out there on the web that discuss this issue- here are a couple of examples:    Walkthrough: Two-way binding inside a XAML User Control and  WPF - Custom UserControl datacontext binding gotcha

What it boils down to is this: If you need to set content on the User Control from its own properties AND from dependency properties which are bound to some other property on the parent (eg your dockpane View Model) then don't do this:

public partial class StatusRuler : UserControl {

    public StatusRuler() {
       InitializeComponent();
       this.DataContext = this;//breaks the chain....dependecy properties 
                               //bound to the dockpane won't work

Instead, do this:

public partial class StatusRuler : User Control {

   public StatusRuler() {
      InitializeComponent();

      (this.Content as FrameworkElement).DataContext = this;//Do this. Bind to inherited
                                                            //"Content"‍‍‍‍‍‍‍ property.

You can also give your root element in the User Control, typically a Grid, an "x:Name", and bind its data context to accomplish the same thing - this.TheNameOfMyGrid.DataContext = this;

0 Kudos