WPF GridSplitter after resize - wpf

Once the gridsplitter is used to resize a grid the row * will not reclaim the space when the other rows are collapsed.
I have the following grid in a master detail view with three rows. A data grid on top a splitter in the middle and a contentcontrol view in the last row. The splitter has a close button on it to collapse the detail. This all works with the exception that once the user resizes using the gridsplitter.
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Style="{StaticResource CollapsableRow}"/><!-- Splitter Here -->
<RowDefinition Style="{StaticResource CollapsableRow}"/>
</Grid.RowDefinitions>
The GridSplitter style:
<Style x:Key="gridSplitterStyle" TargetType="{x:Type GridSplitter}">
<Setter Property="Visibility" Value="{Binding IsItemSelected, Converter={StaticResource BoolToShow},ConverterParameter='Visible|Collapsed'}" />
<Setter Property="Width" Value="Auto"/>
<Setter Property="Height" Value="14"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Border.BorderBrush" Value="#FF6593CF" />
<Setter Property="Border.BorderThickness" Value="0,1,0,0" />
<Setter Property="UIElement.SnapsToDevicePixels" Value="True" />
<Setter Property="UIElement.Focusable" Value="False" />
<Setter Property="Control.Padding" Value="7,7,7,7" />
<Setter Property="Cursor" Value="SizeNS" /></Style>
Like I said the collapse works correctly unless the gridsplitter is used to resize. After that the whitespace stays.
EDIT:
H.B. and codenaked had simple and consistant suggestions so and I attempted to implement them w/o success in a data trigger:
<Style x:Key="CollapsableRow" TargetType="{x:Type RowDefinition}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="True">
<Setter Property="RowDefinition.Height" Value="0"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem, Converter={StaticResource IsNullConverter}}" Value="False">
<Setter Property="RowDefinition.Height" Value="Auto"/>
</DataTrigger>
</Style.Triggers>
</Style>

Since the grid splitter and detail were already being hidden Visibility was the obvious choice to reset the next row definition height.
/// <summary>
/// Grid splitter that show or hides the following row when the visibility of the splitter is changed.
/// </summary>
public class HidableGridSplitter : GridSplitter {
GridLength height;
public HidableGridSplitter()
{
this.IsVisibleChanged += HideableGridSplitter_IsVisibleChanged;
this.Initialized += HideableGridSplitter_Initialized;
}
void HideableGridSplitter_Initialized(object sender, EventArgs e)
{
//Cache the initial RowDefinition height,
//so it is not always assumed to be "Auto"
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
height = lastRow.Height;
}
void HideableGridSplitter_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
Grid parent = base.Parent as Grid;
if (parent == null) return;
int rowIndex = Grid.GetRow(this);
if (rowIndex + 1 >= parent.RowDefinitions.Count) return;
var lastRow = parent.RowDefinitions[rowIndex + 1];
if (this.Visibility == Visibility.Visible)
{
lastRow.Height = height;
}
else
{
height = lastRow.Height;
lastRow.Height = new GridLength(0);
}
}

You can use animation to solve the row/column definition override by gridsplitter. See my answer to a similar question at GridSplitter overrides ColumnDefinition's style trigger?

If you use the GridSplitter the Heights are no longer Auto but concrete values. You need to manually change the values back either using a style or events and code behind, e.g. this resets a auto-size column on double-click:
private void ColumnSplitter_DoubleClick(object sender, MouseButtonEventArgs e)
{
if (!ColumnTreeView.Width.IsAuto) ColumnTreeView.Width = new GridLength();
}

Based on what you provided, the GridSplitter will resize the previous and next rows. You can see this in action with this code:
<Grid Margin="3,0">
<Grid.RowDefinitions>
<RowDefinition x:Name="row0" Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition x:Name="row2" Height="Auto" />
</Grid.RowDefinitions>
<Border Background="Red" >
<TextBlock Text="{Binding ElementName=row0, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<GridSplitter Grid.Row="1" Style="{StaticResource gridSplitterStyle}" HorizontalAlignment="Stretch" />
<Border Background="Blue" Grid.Row="2" MinHeight="50">
<TextBlock Text="{Binding ElementName=row2, Path=Height}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</Grid>
The last row's size will actually change from Auto to a fixed height. So even if you collapse the content in that row, it will still take up the specified space. You'd need to reset the row to Height="Auto" to truly collapse it with it's content.

Related

WPF XAML Local (child) styles are being overwritten. Why?

I wrote a simple dialog (XAML/WPF) and a test app and the dialog looks fine. In particular the buttons in the ListView have rounded corners. I've posted a picture and the code below.
The problem? When I use this dialog inside a much larger program (codebase too large to share), the rounded corners and other styling is gone. I strongly suspect something in the larger program is taking precedence over my local work. Perhaps a global style for buttons or some such thing?
I'd like to understand what is going on. Presumably something in the main app takes precedence over my xaml work?
I'd like to know if there is a way to say "don't inherit styles from the app itself. Rather use WPF defaults unless I override them.", assuming that is the problem.
See picture (notice rounded corners)
See picture from when I call it from actual main application instead of test application
Notice in particular lack of rounded corners. My work to produce rounded corners is gone! Also, in the test app, hovering over a button shows a blue color which I assume is default (I didn't do it). When called from main app, no such hover effect. I suspect the main app gets rid of that somewhere.
Here's the simple dialog xaml
<Window x:Class="FirmsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Dialogs"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="400"
Width="390" Height="720" BorderBrush="LightGray"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
x:Name="FirmsViewDlg" Loaded="FirmsViewDlg_Loaded"
>
<Window.Resources>
<FontFamily x:Key="AvenirNextforCompany">
pack://application:,,,/Assets/Fonts/#AvenirNextforCompany
</FontFamily>
<local:ReverseObjectToBool x:Key="ReverseObjectToBoolConverter" />
<local:ObjectToBool x:Key="ObjectToBoolConverter" />
<!-- New style -->
<Style x:Key="StyleListViewItem" TargetType="ListViewItem">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
<!-- Existing style -->
<Style x:Key="StyleListView" TargetType="ListView">
<Setter Property="ItemContainerStyle" Value="{StaticResource StyleListViewItem}"/>
<Setter Property="SnapsToDevicePixels" Value="true"/>
<!-- ... -->
</Style>
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="Select Company" HorizontalAlignment="Center" FontSize="20px" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontWeight="Normal" ></Label>
<Label Grid.Row="1" Content="Accountant companies" Margin="10,0" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" Foreground="#8d9096" FontWeight="Normal"></Label>
<ListView BorderThickness="0" Grid.Row="2" ItemsSource="{Binding RealmMembershipInfo}" SelectedItem="{Binding SelectedFirm}" x:Name="realmListBox"
HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch" Height="Auto" Margin="0,0,0,0"
VerticalAlignment="Top" FontWeight="Bold" FontSize="14" Background="White"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
BorderBrush="LightGray">
<ListView.ItemTemplate>
<DataTemplate>
<Button Click="Button_Click"
MinHeight="65" Padding="10,0,10,0"
Margin="0,0,0,0" HorizontalContentAlignment="Left" HorizontalAlignment="Stretch" BorderBrush="LightGray" Background="White" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="SemiBold"
>
<Button.Resources>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="9"/>
</Style>
</Button.Resources>
<TextBlock Text="{Binding displayName}" TextWrapping="Wrap" HorizontalAlignment="Stretch" Foreground="#393a3d" FontFamily="{StaticResource AvenirNextforCompany}" FontSize="14px" FontWeight="Bold" >
</TextBlock>
</Button>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Margin" Value="10,10,10,10"/>
<Setter Property="Padding" Value="0"/>
<Style.Triggers>
<Trigger Property="Control.IsMouseOver" Value="True">
<Setter Property="Control.Background" Value="Transparent" />
<Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="BorderThickness" Value="0" />
</Trigger>
</Style.Triggers>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Grid>
</Window>
Simple program to show the Xaml above
using Dialogs;
using Models;
using System.Collections.Generic;
using System.Windows;
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_SelectFirm(object sender, RoutedEventArgs e)
{
// hack code to set up dialog
RealmMembershipInfo realmInfo = new RealmMembershipInfo();
realmInfo.realmMembershipInfo = new List<RealmMembershipItem>();
RealmMembershipItem item = new RealmMembershipItem();
item.displayName = "Company 1";
realmInfo.realmMembershipInfo.Add(item);
item = new RealmMembershipItem();
item.displayName = "Company2";
realmInfo.realmMembershipInfo.Add(item);
FirmsDialog dlg = new FirmsDialog(realmInfo);
dlg.ShowDialog();
MessageBox.Show("Your picked firm: " + dlg.SelectedFirm);
}
private void Button_SelectClient(object sender, RoutedEventArgs e)
{
}
}
}
You haven't set the style on the Listview.
<ListView Style={StaticResource StyleListView} ....
And you are using the default style for the ItemsContainer. You need to add the a base style.
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem" BasedOn={StaticResource StyleListViewItem}...

WPF: Dynamically hiding a grid column doesn't always work correctly using Styles or IValueConverter

I have a Grid with three columns - left, right, and a grid splitter in between. I need to hide one of the panels. What I need is to set Width=0 for two Splitter columns, and for the Panel column. However, this approach works well only from code. When I use styles or value converters, it works only in some cases.
Everything works as expeted until I move the splitter, panels hide but leave white space (for the case #2/Styles), or doesn't hide one of the panels (for case #3/IValueConverter). Only the "code behind" way works correctly in all cases. The GIF image below illustrates the behavior.
The code is shown below. The main idea is to set Width and MaxWidth properties for grid colums to 0, and then back to * for the panel, and to Auto for Splitter.
1. The way it works (code behind):
private void TogglePanelVisibility(bool isVisible)
{
if (isVisible)
{
// Restore saved parameters:
_mainWindow.ColumnPanel.Width = new GridLength(_columnPanelWidth, GridUnitType.Star);
_mainWindow.ColumnPanel.MaxWidth = double.PositiveInfinity;
_mainWindow.ColumnSplitter.Width = new GridLength(_columnSplitterWidth, GridUnitType.Auto);
_mainWindow.ColumnSplitter.MaxWidth = double.PositiveInfinity;
return;
}
// Save parameters:
_columnSplitterWidth = _mainWindow.ColumnSplitter.Width.Value;
_columnPanelWidth = _mainWindow.ColumnPanel.Width.Value;
// Hide panel:
_mainWindow.ColumnPanel.Width = new GridLength(0);
_mainWindow.ColumnPanel.MaxWidth = 0;
_mainWindow.ColumnSplitter.Width = new GridLength(0);
_mainWindow.ColumnSplitter.MaxWidth = 0;
}
2. The way it doesn't work (XAML Style)
<Window.Resources>
<Style x:Key="showColumnStar" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="*" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="showColumnAuto" TargetType="{x:Type ColumnDefinition}">
<Style.Setters>
<Setter Property="Width" Value="Auto" />
<Setter Property="MaxWidth" Value="{x:Static system:Double.PositiveInfinity}" />
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding IsPanelVisible}" Value="False">
<DataTrigger.Setters>
<Setter Property="Width" Value="0" />
<Setter Property="MaxWidth" Value="0" />
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<!-- ... -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="*" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
3. And the last scenario using value converters
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='Auto'}" />
<ColumnDefinition Width="{Binding IsPanelVisible, Converter={StaticResource BoolToGridSizeConverter}, ConverterParameter='*'}" />
</Grid.ColumnDefinitions>
<!-- ... -->
</Grid>
C#:
internal class BoolToGridRowColumnSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var param = parameter as string;
var unitType = GridUnitType.Star;
if (param != null && string.Compare(param, "Auto", StringComparison.InvariantCultureIgnoreCase) == 0)
{
unitType = GridUnitType.Auto;
}
return ((bool)value == true) ? new GridLength(1, unitType) : new GridLength(0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There is one more thing I learning from debugging the case #3 - on an attempt to show or hide the panel after moving the splitter, the Converted is calling only once.
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
The full source code of the demo project are on GitHub.
Thank you in advance!
Could you please tell me what is happening, why the cases #2 and #3 do not work properly?
Because the GridSplitter sets the local value of the Width property of the ColumnDefinition and local values always take precedence over style setter values as explained in the docs.
So you have to set the local value of the Width property like you do in the code-behind.
I cloned your Code:
as for #2:
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnAuto}" />
<ColumnDefinition Width="Auto" Style="{StaticResource showColumnStar}" />
</Grid.ColumnDefinitions>
setting the 3rd column to "Auto" did the trick
for #3:
if you debug your solution and set a breakpoint in your converter, you will see, that after moving your Splitter in the 3rd block, the converter is only reached once, so for the column with the splitter. Moving the splitter destroys your Width-binding on the 3rd Column (as #mm8 pointed out in his answer).
so either you manage to redefine your Binding after the splitter moved (e.g. PreviewMouseLeftButtonUp event) or just let this approach go.

wpf toggling visibility collapse only works once

What i want to do is collapse the bottom section of the WPF ui based on the checkbox toggle. This mostly works as expected so far. Below as you step through the images you'll see the collapse stops working once the grid splitter is moved. I do not understand why or how to fix this.
Start of the application, appears correct.
Toggle the checkbox and the botttom section disappears as expected.
Toggle the checkbox once more and then move the splitter vertically, everything appears as expected.
Now toggle the checkbox one last time and you'll notice the top section doesn't fill the application like it once did before. This is where it appears broken! I would expect the yellow section to fill the UI like it did in the initial toggle.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="250" Width="525"
WindowStartupLocation="CenterScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<CheckBox Name="ToggleVisibility" Margin="10" IsChecked="True" Content="Toggle Bottom Section"></CheckBox>
<StackPanel Background="#feca00" Grid.Row="1">
<TextBlock FontSize="20" Foreground="#58290A">Top Section</TextBlock>
</StackPanel>
<GridSplitter Grid.Row="2" Height="5" HorizontalAlignment="Stretch">
<GridSplitter.Style>
<Style TargetType="GridSplitter">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</GridSplitter.Style>
</GridSplitter>
<StackPanel Grid.Row="3" Background="LightBlue">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Visible" />
<Setter Property="Background" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=ToggleVisibility, Path=IsChecked}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
<Setter Property="Background" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock FontSize="20" Foreground="black">Bottom Section</TextBlock>
</StackPanel>
</Grid>
</Window>
The reason why this happens is that when you move the splitter it overwrites the Height property of the last RowDefinition and sets it to actual height of whatever is presented in the according row of the Grid - the second StackPanel (bottom section) in your case. It no longer is set to Auto, so even if you collapse the bottom section the row maintains it's height - hence the empty space.
I sometimes find myself in a similar situation, and what I do then is to span the control, which I want to take up all the space, across the remaining rows using Grid.RowSpan attached property. In your case you could achieve it by applying the following style to your first StackPanel (top section):
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=ToggleVisibility}" Value="False">
<Setter Property="Grid.RowSpan" Value="3" />
</DataTrigger>
</Style.Triggers>
</Style>
Then the StackPanel will span to the bottom of the Grid regardless of the following rows' heights.
EDIT
Since you're not satisfied with spanning the control, here's another solution: derive from RowDefinition class and wire your own visibility logic. Here's an example:
public class MyRowDefinition : RowDefinition
{
static MyRowDefinition()
{
HeightProperty.OverrideMetadata(
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
CoerceValueCallback = CoerceHeightPropertyValue
});
}
private static object CoerceHeightPropertyValue(DependencyObject d, object baseValue)
{
if (Equals(d.GetValue(IsVisibleProperty), false))
return new GridLength(0d);
else
return baseValue;
}
public bool IsVisible
{
get { return (bool)GetValue(IsVisibleProperty); }
set { SetValue(IsVisibleProperty, value); }
}
public static readonly DependencyProperty IsVisibleProperty =
DependencyProperty.Register(
"IsVisible",
typeof(bool),
typeof(MyRowDefinition),
new FrameworkPropertyMetadata
{
DefaultValue = true,
PropertyChangedCallback = IsVisiblePropertyChanged
});
private static void IsVisiblePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.InvalidateProperty(RowDefinition.HeightProperty);
}
}
Then use that class instead of RowDefinition in your grid and bind the IsVisible property to the check state of the checkbox.
It looks like the StackPanel and the GridSplitter are collapsed, but that Grid.Row 3 is not. I'm not sure why the Auto height does no close it out. EDIT: I see that #Grx70 has explained it.
This: Hide grid row in WPF
suggests that setting all children to Visibility.Collapsed ought to work, but you have the option of setting the height of Grid.Row 3 to 0.
I think the Height="5" of your GridSplitter might also keep it taking up space, taking precedence over the style setting (it is collapsed and gets Height 0 from that, but gets Height 5 from the Height="5", which wins (but it is still invisible). It might be better to but that in the style too:
<Setter Property="Height" Value="5" />

Make Silverlight DataGridCell contents fill the cell?

I am trying to recognize a MouseLeftButtonDown on every cell in a DataGrid, however two things are stopping me:
DataGridCell doesn't fire click events
Using a grid (with click event) as the child element won't fill the cell, and therefore is only clickable on its content which it auto-sizes itself to fit.
Here's my cell template:
<DataTemplate x:Key="...">
<Grid MouseLeftButtonDown="..."
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="24" Height="24"
Source="..."/>
</Grid>
</DataTemplate>
Row heights/column widths mean a cell is 64px high and 80px wide, and resizable, so the 24x24 image inside is the only clickable portion in a potentially large cell. The DataGridCell itself is styled as follows:
<Style TargetType="data:DataGridCell" x:Key="...">
<Setter Property="Height" Value="64"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
Ideally, the grid should expand to the size of the containing cell. But no matter how many times I assign the alignment properties to stretch, it won't. You can see by setting the Grid's background. It fits to its content, no more.
I have managed to hack together a solution by placing transparent Canvas elements in columns 0 and 2 and setting a column maxwidth, but I feel like there must be a better way.
Actually, the Grid has already fill the cell.You can set a background to the Grid like :
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" Background="Red"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" >
Then try to click it, the MouseLeftButtonDown event will be fired.
The reason is, the Grid's Background is null in default, it means there is no drawing area on UI, so no event will be fired. You can set Background="Transparent" instead.
Background="Transparent" is different from Background= null, Background="Transparent" means the control has a background and there is a drawing area.
here is my example:
<Window.Resources>
<Style TargetType="DataGridCell">
<Setter Property="Height" Value="64"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</Window.Resources>
<DataGrid x:Name="dg">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown" Background="Red"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="24" Height="24"
Source="add.png"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public MainWindow()
{
InitializeComponent();
List<string> lst = new List<string>() { "123" };
dg.ItemsSource = lst;
}
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show("hit");
}

Moving ListBoxItems around a Canvas?

I'm working on dragging objects around a Canvas, which are encapsulated in ListBoxItems -- the effect being to create a simple pseudo desktop.
I have a ListBox with a Canvas as the ItemsPanelTempalte, so that the ListBoxItems can appear anywhere on screen:
<ListBox ItemsSource="{Binding Windows}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I have a Style to define how the ListBoxItems should appear:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}" />
<Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<local:PseudoWindowContainer Content="{TemplateBinding Content}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The "PseudoWindowContainer" extends from the ContentControl and has its own Style applied to make it look like a dialog box (title bar, close button, etc...). Here is a chunk of it:
<Style TargetType="{x:Type local:PseudoWindowContainer}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="Width" Value="{Binding Width, Mode=TwoWay}" />
<Setter Property="Height" Value="{Binding Height, Mode=TwoWay}" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PseudoWindowContainer}">
<Grid Name="LayoutRoot" Background="White">
<!-- ... snip ... -->
<Border Name="PART_TitleBar" Grid.Row="0" Background="LightGray" CornerRadius="2,2,0,0" VerticalAlignment="Stretch" Cursor="Hand" />
<TextBlock Name="TitleBar_Caption" Text="{Binding DisplayName}" Grid.Row="0" Background="Transparent" Padding="5,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" />
<Button Name="TitleBar_CloseButton" Command="{Binding CloseCommand}" Grid.Row="0" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="0,5,5,0" Width="20" Height="20" Cursor="Hand" Background="#FFFF0000" Foreground="#FF212121" />
<!-- ContentPresenter -->
<ContentPresenter Grid.Row="1" />
<!-- ... snip ... -->
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="WindowBorder" Property="Background" Value="Blue" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter TargetName="WindowBorder" Property="Background" Value="#22000000" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
Inside the PseudoWindowContainer.cs class I create some event handlers to listen for MouseDown/MouseUp/MoveMove events:
public override void OnApplyTemplate()
{
_titleBar = (Border)Template.FindName("PART_TitleBar", this);
if (_titleBar != null)
{
_titleBar.MouseDown += TitleBar_MouseDown;
_titleBar.MouseUp += TitleBar_MouseUp;
}
_grip = (ResizeGrip)Template.FindName("PART_ResizeGrip", this);
if (_grip != null)
{
_grip.MouseLeftButtonDown += ResizeGrip_MouseLeftButtonDown;
_grip.MouseLeftButtonUp += ResizeGrip_MouseLeftButtonUp;
}
base.OnApplyTemplate();
}
private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove += TitleBar_MouseMove;
((Border)sender).CaptureMouse();
_windowLocation.X = Left;
_windowLocation.Y = Top;
_clickLocation = this.PointToScreen(Mouse.GetPosition(this));
}
private void TitleBar_MouseUp(object sender, MouseButtonEventArgs e)
{
_titleBar.MouseMove -= TitleBar_MouseMove;
((Border)sender).ReleaseMouseCapture();
}
private void TitleBar_MouseMove(object sender, MouseEventArgs e)
{
Point currentLocation = this.PointToScreen(Mouse.GetPosition(this));
Left = _windowLocation.X + currentLocation.X - _clickLocation.X;
Top = _windowLocation.Y + currentLocation.Y - _clickLocation.Y;
}
The trouble I run into is the "Left" and "Top" are not defined properties, and updating them to Canvas.SetLeft/SetTop (or GetLeft/GetTop, accordingly) does not update the position on the Canvas.
I have "Left" and "Top" defined in the ViewModel of the controls I place into the ListBoxItems, and are thus subsequently wrapped with a PseudoWindowContainer because of the Template. These values are being honored and the objects do appear in the correct location when the application comes originally.
I believe I need to somehow define "Left" and "Top" in my PseudoWindowContainer (aka: ContentControl) and have them propagate back up to my ViewModel. Is this possible?
Thanks again for any help!
I've found a solution to the problem. Instead of typing it all out again, I will point to the MSDN Forum post that describes what I did:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/d9036b30-bc6e-490e-8f1e-763028a50153
Did you read article by Bea Stollnitz: The power of Styles and Templates in WPF?
That is an example of Listbox where items are drawn at specific coordinates calculated in Converter.

Resources