I am building a WPF application in C#.
I am trying to implement a two column layout using Grid
layout, similar to a browser sidebar.
Grid
is implemented as a 3-column layout because I am using a GridSplitter
to easily change the width of the sidebar.
The sidebar can be collapsed, so the Style is set up with reference to the following so that it can be collapsed without problems after using GridSplitter
.
WPF: collapse GridSplitter?
By default, the sidebar is placed on the right, but I would like to reverse the order dynamically in code.
MainContent | GridSplitter | Sidebar
<---->
Sidebar | GridSplitter | MainContent
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800"
d:DataContext="{d:DesignInstance local:MainWindow}">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<!--Main Column-->
<ColumnDefinition Width="*" />
<!--GridSplitter -->
<ColumnDefinition Width="Auto" />
<!--Collapsable Sidebar Column -->
<!--Can collapse entirely even if GridSplitter is used -->
<!--ref: https://stackoverflow.com/questions/12483017/wpf-collapse-gridsplitter-->
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="200"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSidebar}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0"/>
<Setter Property="MaxWidth" Value="0"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
</Grid.ColumnDefinitions>
<!--Main Column-->
<Grid Grid.Column="0" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Button Content="Toggle sidebar" Click="ToggleSidebarButton_OnClick" />
<Button Content="Swap sidebar" Click="SwapSidebarButton_OnClick" />
</StackPanel>
<TextBlock
Grid.Row="1"
Text="Main Content"
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
<GridSplitter
Grid.Column="1"
Visibility="{Binding ShowSidebar, Converter={StaticResource BooleanToVisibilityConverter}}"
Width="5"
ShowsPreview="True"
ResizeBehavior="PreviousAndNext"
ResizeDirection="Columns" />
<!--Sidebar Column-->
<Grid
Grid.Column="2"
Visibility="{Binding ShowSidebar, Converter={StaticResource BooleanToVisibilityConverter}}"
Background="Gray">
<TextBlock
Text="Sidebar"
FontSize="32"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _showSidebar = true;
public bool ShowSidebar
{
get => _showSidebar;
set => SetField(ref _showSidebar, value);
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private void ToggleSidebarButton_OnClick(object sender, RoutedEventArgs e)
{
ShowSidebar = !ShowSidebar;
}
private void SwapSidebarButton_OnClick(object sender, RoutedEventArgs e)
{
// TODO: How to swap the sidebar programmatically?
throw new NotImplementedException();
}
#region IPC
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
#endregion
}
}
The TODO part is written in code-behind, but I would actually like to implement it in ViewModel with MVVM.
I have confirmed that it is possible to implement this by using 5-column layout as shown below, implemented by creating two variables ShowSidebarRight
and ShowSidebarLeft
, placing two SidebarView and GridSplitter each, and controlling the display with Visibility
.
but I would like to implement it with only one instance of View since it would create an extra instance of SidebarView and ViewModel, which would cause performance concerns and redundancy in the code.
<Grid.ColumnDefinitions>
<!--Left Sidebar -->
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="200"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSidebarLeft}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0"/>
<Setter Property="MaxWidth" Value="0"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
<!--Left GridSplitter -->
<ColumnDefinition Width="Auto" />
<!--Main Column-->
<ColumnDefinition Width="*" />
<!--Right GridSplitter -->
<ColumnDefinition Width="Auto" />
<!--Right Sidebar -->
<ColumnDefinition>
<ColumnDefinition.Style>
<Style TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="200"/>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowSidebarRight}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0"/>
<Setter Property="MaxWidth" Value="0"/>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
</Grid.ColumnDefinitions>
You can reverse the horizontal layout order by setting the FlowDirection
property:
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding SideBarLeft}" Value="True">
<Setter Property="FlowDirection" Value="RightToLeft"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="200"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" FlowDirection="LeftToRight">
<TextBlock Text="Main Content"/>
</Grid>
<Grid Grid.Column="2" FlowDirection="LeftToRight">
<TextBlock Text="Sidebar"/>
</Grid>
<GridSplitter
Grid.Column="1"
Width="5"
ShowsPreview="True"
ResizeBehavior="PreviousAndNext"
ResizeDirection="Columns" />
</Grid>