Docking WPF controls to right or bottom of a Canvas

28 Jan

When first learning WPF one thing that stumped me was docking controls to the right hand side of a canvas. As I understand WPF layouts, the only default WPF control that allows controls to overlap is the canvas layout control (See comments about a correction here. The canvas control is not the only control that allows items to overlap). This is why I was forced to use the canvas layout (as I wanted the ability to place controls over the top of each other).

The way to solve this issue was to use a multibinding and a multibinding converter. I have provided a code sample below showing a treeview control that is docked to bottom right side of a wpf window.

<TreeView x:Name="Menu" DataContext="MenuData" Height="350" Width="220">
  <Canvas.Left>
    <MultiBinding Converter="{StaticResource RelativePlacementConverter}">
      <Binding ElementName="MainCanvas" Path="ActualWidth" />
      <Binding ElementName="Menu" Path="ActualWidth" />
    </MultiBinding>
  </Canvas.Left>
  <Canvas.Top>
    <MultiBinding Converter="{StaticResource RelativePlacementConverter}">
      <Binding ElementName="MainCanvas" Path="ActualHeight" />
      <Binding ElementName="Menu" Path="ActualHeight" />
    </MultiBinding>
  </Canvas.Top>
</TreeView>

public class RelativePlacementConverter : IMultiValueConverter

{

#region IMultiValueConverter Members

public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)

{

double? screenWidth = values[0] as double?; //parent width

double? menuWidth = values[1] as double?; //own width

if (screenWidth != null && menuWidth != null)

{

return (screenWidth – menuWidth);

}

return 0.0;

}

public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)

{

throw new NotImplementedException();

}

#endregion

}

The previous code uses the width and height of the treeview control called Menu and subtracts that from the width and height of the canvas called MainCanvas. For more usecases or more depth on how to implement a multibinding I recommend the following: wpf-tutorial-using-multibindings.

I would recommed using multibindings when a value needs to be result of a combination of other values. There are other ways around this issue without using a multibinding. Links to APIs to avoid multibindings are avalible at: http://marlongrech.wordpress.com/2008/02/10/embed-code-in-xaml/. I like the idea of avoiding multibindings as they take a bit more effort that should be required for simple scenarios like this blog post, however I am also sceptical of alternatives because they don’t yet seem to leverage the power of code behind the way that the multivalueconverter allows.

Advertisements

3 Responses to “Docking WPF controls to right or bottom of a Canvas”

  1. Michael Hays March 10, 2009 at 5:08 pm #

    I’m glad you got a chance to play with multibindings, but the statement that Canvas is the only panel to allow overlapping controls is (thankfully) false.

    Here is an example in pure XAML to accomplish the same thing. I did it first as a DockPanel and then again using a grid. The first requires fewer lines but requires that you know the width of your overlapping control. The second, in my opinion, is superior because it assumes nothing.

  2. Michael Hays March 10, 2009 at 5:09 pm #

    (Grid)
    (Grid.RowDefinitions)
    (RowDefinition Height=”*”/)
    (RowDefinition Height=”*”/)
    (/Grid.RowDefinitions)

    (!– APPROACH #1 –)
    (DockPanel)
    (Rectangle Panel.ZIndex=”100″ Opacity=”0.5″ DockPanel.Dock=”Right” Width=”200″ Height=”200″ Fill=”Red” VerticalAlignment=”Top”/)
    (TextBox Margin=”0,0,-200,0″ AcceptsReturn=”True” TextWrapping=”Wrap”/)
    (/DockPanel)

    (!– APPROACH #2 –)
    (Grid Grid.Row=”1″)
    (Grid.ColumnDefinitions)
    (ColumnDefinition Width=”0.8*”/)
    (ColumnDefinition Width=”0.2*”/)
    (/Grid.ColumnDefinitions)

    (TextBox Grid.ColumnSpan=”2″ AcceptsReturn=”True” TextWrapping=”Wrap”/)
    (Rectangle Grid.Column=”1″ Opacity=”0.5″ Height=”200″ Fill=”Red” VerticalAlignment=”Top”/)
    (/Grid)
    (/Grid)

  3. rhwilburn March 10, 2009 at 8:05 pm #

    Thanks for the response. I like the implementation of your #2 approach. It is certainly easier to setup than my binding method. My binding method might be better suited to the rare case where a docking pane needs to be moved around. This would allow for reasoning upon the bindings and using service locator like techniques to relocate the docking pane rather than requiring a hardcore style template or requiring a reference to the grid control.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: