1

Since my WPF application uses GridSplitters on several occasions, I want to extract that XAML snippet into a separate UserControl.

Of course, using a ResourceDictionary would be nicer. But that way, I can only define a ControlTemplate for the splitter's content and use it within the Template-attribute afterwards - which removes the ability to define all those GridSplitter attributes only once and then consecutively use them.

The GridSplitter UserControl, GridSplitter.xaml:

<GridSplitter HorizontalAlignment="Stretch" Margin="3" ResizeBehavior="PreviousAndNext"
              ResizeDirection="Columns" VerticalAlignment="Stretch">
  <GridSplitter.Template>
    <ControlTemplate TargetType="{x:Type GridSplitter}">
      <Grid>
        <Button Content="⁞" />
        <Rectangle Fill="#00FFFFFF" />
      </Grid>
    </ControlTemplate>
  </GridSplitter.Template>
</GridSplitter>

The usage in MainWindow.xaml:

<Window
         (...)
         xmlns:uc="clr-namespace:Yoda.Frontend.Resources"
         (...)>
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition MinWidth="100" Width="200" />
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition MinWidth="400" Width="*" />
    </Grid.ColumnDefinitions>
    <!-- (...) -->
    <uc:GridSplitter Grid.Column="1" />
    <!-- (...) -->
  </Grid>
    <!-- (...) -->
</Window>

The result of using the above code is a splitter which can't be moved in any direction.

However, using the ResourceDictionary dictionary approach mentioned above, I get a movable GridSplitter.
But despite working perfectly directly in MainWindow.xaml, it only resizes the third grid column.

Sure, setting Width isn't recommended when using GridSplitter. But why does it work whenever the splitter is defined within the main window and only fails to do so when used as an UserControl? And how to fix that in an MVVM, no code-behind way?

Jim
  • 2,974
  • 2
  • 19
  • 29
Yoda
  • 539
  • 1
  • 9
  • 18

1 Answers1

1

In case you only what to have a nice looking splitter, you can use this code:

<Grid.ColumnDefinitions>
     <ColumnDefinition MinWidth="100" Width="Auto" />
     <ColumnDefinition MinWidth="20" Width="Auto" />
     <ColumnDefinition MinWidth="400" Width="Auto" />
</Grid.ColumnDefinitions>
<!-- (...) -->
<GridSplitter Grid.Column="1" Background="Gray" HorizontalAlignment="Stretch"
              VerticalAlignment="Stretch" />
<TextBlock Text="⁞" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center"
           IsHitTestVisible="False" />
<!-- (...) -->

Another solution would be to define the ControlTemplate as a resource and then use that for the GridSplitter:

<Window.Resources>
    <ControlTemplate TargetType="{x:Type GridSplitter}" x:Key="gridSplitter">
        <Grid Background="Transparent">
            <Button Content="⁞" IsHitTestVisible="False" />
            <Rectangle Fill="#00FFFFFF" IsHitTestVisible="False" />
        </Grid>
    </ControlTemplate>
</Window.Resources>
<Grid>
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Blue">
        <Grid.ColumnDefinitions>
            <ColumnDefinition MinWidth="100" Width="Auto" />
            <ColumnDefinition MinWidth="20" Width="Auto" />
            <ColumnDefinition MinWidth="400" Width="Auto" />
        </Grid.ColumnDefinitions>
        <!-- (...) -->
        <GridSplitter Grid.Column="1" Template="{StaticResource gridSplitter}" />
        <!-- (...) -->
    </Grid>
    <!-- (...) -->
</Grid>

There's also a solution if you really want to use the set properties within the splitter template: use a style to add those. It would look like this:

<Window.Resources>
    <ControlTemplate TargetType="{x:Type GridSplitter}" x:Key="gridSplitter">
        <Grid Background="Transparent">
            <Button Content="⁞" IsHitTestVisible="False"/>
            <Rectangle Fill="#00FFFFFF" IsHitTestVisible="False"/>
        </Grid>
    </ControlTemplate>
    <Style TargetType="{x:Type GridSplitter}" x:Key="styleGridSplitter">
        <Setter Property="Template" Value="{StaticResource gridSplitter}" />
        <Setter Property="ResizeBehavior" Value="PreviousAndNext" />
        <Setter Property="ResizeDirection" Value="Columns" />
        <Setter Property="VerticalAlignment" Value="Stretch" />
        <Setter Property="HorizontalAlignment" Value="Stretch" />
        <Setter Property="Margin" Value="3" />
    </Style>
</Window.Resources>
<Grid>
    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Blue">
        <Grid.ColumnDefinitions>
            <ColumnDefinition MinWidth="100" Width="Auto" />
            <ColumnDefinition MinWidth="20" Width="Auto" />
            <ColumnDefinition MinWidth="400" Width="Auto" />
        </Grid.ColumnDefinitions>
        <!-- (...) -->
        <GridSplitter Grid.Column="1" Style="{StaticResource styleGridSplitter}"/>
        <!-- (...) -->
    </Grid>
    <!-- (...) -->
</Grid>

Yoda
  • 539
  • 1
  • 9
  • 18
Lupu Silviu
  • 1,145
  • 11
  • 23
  • First of all: thanks _a lot_ for this _awesome_ and diverse answer. I mixed up that `ControlTemplate`s need to be defined within a `Style`, not the other way round. From there, I was even able to improve your answer. :) So, I'm happily accepting your answer as soon as you can clarify one remaining issue: why do I have to switch from `StaticResource` to `DynamicResource` in order to avoid runtime exceptions? **Note:** I defined the `GridSplitter` in a separate XAML file for inclusion within App.xaml's `ResourceDictionary.MergedDictionaries` (next to one for the `ContextMenu` that's unrelated). – Yoda Nov 10 '16 at 14:01
  • The solutions I have suggested, does not use a different control, but the default one that is present in WPF. Does your solution now still use a control made by you? – Lupu Silviu Nov 10 '16 at 14:08
  • No, I simply switched to your 3rd solution (with the addition that I mentioned in my first comment regarding a separate file and including that file within App.xaml's `ResourceDictionary.MergedDictionaries`). BTW: Thanks for hinting me at `IsHitTestVisible`! :) – Yoda Nov 10 '16 at 14:57
  • 1
    StaticResource are not reactive to Merge of dictionaries. The Merge is done at runtime, at some time. DynamicResource is reactive to Merge. I think that this is the cause. – Lupu Silviu Nov 10 '16 at 15:24
  • Awesome answer! FWIW, it's explained in a bit more detail [here](https://stackoverflow.com/questions/4325015/wpf-problem-with-static-resource-shared-in-merged-dictionaries/4326738#4326738). And as promised before, your perfect answer is marked accepted now. Thanks a lot! :) – Yoda Nov 11 '16 at 11:35