I need to position a checkbox above a column in a datagrid, and keep the checkbox aligned with the left edge of the column, even if the columns are resized (or reordered) by the user.
Putting the checkbox in the column header is probably not an option.
The first challenge is knowing when the user is resizing a column.
There is a ColumnHeaderDragStarted event, but I think that is raised when the user drags a column header to reorder it. Does it also apply to a column resize operation?
The next challenge is re-positioning the checkbox as the column is moved. How can the position of the left edge of the column ("Val1", for example) be determined w/r/t to the left edge of the containing grid? I suspect that if that value can be determined, it can be used to position the checkbox.
Thanks for any insights in to this problem --
UPDATE 2
This xaml is providing much of the desired behavior, but the alignment isn't right. If the user move the separator between "Id" and "Val1", the checkbox moves.
The goal, however, is to have the checkbox remain aligned above "Id"; it should move only when the separator between "Name" and "Id" is moved.
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition />
</Grid.RowDefinitions>
<DataGrid x:Name="DataGrid"
Grid.Row="1" Grid.Column="0" />
<Canvas Grid.Row="0" Grid.RowSpan="2" >
<CheckBox Content="Check One"
Canvas.Top="10"
Canvas.Left="{Binding Columns[1].ActualWidth, ElementName=DataGrid}" />
</Canvas
</Grid>
UPDATE:
This seems like it might be a useful approach:
https://stackoverflow.com/a/41094009/107037
The challenge of positioning the checkbox still exists, however...
Since someone asked:
Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="32" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<CheckBox Content="Check One" Grid.Column="1" />
<CheckBox Content="Check One" Grid.Column="2" />
<DataGrid x:Name="DataGrid"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" ></DataGrid>
</Grid>
Related
In the Designer everything looks alright but in Debbuging a weird white rectangle covers a part of it (If I don't set any Margin in the Button, It looks fine).
Designer
While Debugging
<Window x:Class="MyProgram.MainWindow" ResizeMode="NoResize"
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:MyProgram"
mc:Ignorable="d"
Title="MyProgram" Height="450" Width="650">
<Grid>
<Label Content="Things:" HorizontalAlignment="Left" Margin="0,7,0,0" VerticalAlignment="Top" FontSize="22"/>
<Button Height="28" Width="180" Background="Transparent" BorderThickness="0" Margin="454,15,10,378" >
<StackPanel Orientation="Horizontal">
<Image Source="Resources/addpackage2.png" Width="35" />
<TextBlock Text="Add" Margin="3,0,3,0" FontSize="18" />
</StackPanel>
</Button>
</Grid>
I'm doing something wrong?
The margin on your button Margin="454,15,10,378" is constricting the space available for the button. If you make the make the window larger, you will see the whole button.
Edit
There are different ways to organize a layout in XAML, but usually a grid is a good choice.
This is one way you could write it.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Things:" HorizontalAlignment="Left" Margin="7" VerticalAlignment="Top" FontSize="22"/>
<Button Grid.Row="0" Grid.Column="1" Height="28" Width="180" Background="Transparent" BorderThickness="0" >
<StackPanel Orientation="Horizontal">
<Image Source="Resources/addpackage2.png" Width="35" />
<TextBlock Text="Add" Margin="3,0,3,0" FontSize="18" />
</StackPanel>
</Button>
</Grid>
I have added row and column definitions to the grid. In this case two rows and two columns.
The top row is sized automatically according to its content. The second row fills the remaining space.
Similarly, the right hand column is sized automatically according to its content and the left hand column fills the remaining space.
This is just an example. You might want to define a different layout. I presume that the bottom of the screen will not always be white space.
Having defined the rows and columns, you can assign the individual controls to the cells in the grid, by specifying the Grid.Row and Grid.Column properties.
I have placed your label in row 0, column 0 and I have placed the button in row 0 column 1.
Very important, is that I have removed the Margin definition from the button. This kind of positioning controls is, in my opinion, always an error.
I think that the layout is more or less as you wanted.
I have a Listbox that's inside a Grid that's inside a user control. That user control is placed in a tab panel that can resize.
The Listbox is defined as:
<ListBox HorizontalAlignment="Left" Margin="12,42,12,12" Name="listBox1" VerticalAlignment="Top" >
This works great as long as my listbox is populated. When it's not populated, it's a square about 4 pixels wide/tall. If it's not full, it shrinks down to fit whatever is in it.
This is not what I'd like. What I would like is for it to always maintain the margins I've defined. My question is in two parts:
1: Why does it behave the way it does?
2: How do I make it behave the way I want?
Thanks.
Since you're placing this on a Grid control, you should actually use it as intended and create the rows and columns and put your ListBox in the correct cell(s).
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="42" />
<RowDefinition />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12" />
<ColumnDefinition />
<ColumnDefinition Width="12" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listBox1"
Grid.Row="1" Grid.Column="1">
</ListBox>
</Grid>
It is maintaining the margins but the margins are outside the listbox itself so only change the positioning of the listbox relative to its container.
I'm not sure how you would like it to appear but could set a minimum width/height.
<ListBox HorizontalAlignment="Left" Margin="12,42,12,12"
Name="listBox1" MinHeight="10" MinWidth="100" VerticalAlignment="Top" />
or just remove the alignment settings
<ListBox Margin="12,42,12,12" Name="listBox1" />
if you want the listbox to fill its container.
I've been staring at some code for a RowDetailsTemplate definition in one of my Silverlight datagrids and thought about refactoring it because it looked like it had repeating grids. I thought, after looking at this, that I would only need one datagrid, but as it turns out, this wierd, mysterious double grid is required to get all the items to line up:
<data:DataGrid.RowDetailsTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition MinHeight="20" />
<RowDefinition MinHeight="25" />
<RowDefinition MinHeight="25" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Attendee Cap" Margin="0,0,50,0" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding AttendeeCap}"/>
<TextBlock Text="Start Date" Grid.Row="1" Grid.Column="0" Margin="0,0,50,0" />
<controls:DatePicker SelectedDate="{Binding StartDate, Mode=TwoWay}" Grid.Row="1" Grid.Column="1"/>
<input:TimePicker Value="{Binding StartDate, Mode=TwoWay}" Grid.Row="1" Grid.Column="2" MinWidth="65" />
<TextBlock Text="End Date" Grid.Row="2" Grid.Column="0" Margin="0,0,50,0" />
<input:TimePicker Value="{Binding EndDate, Mode=TwoWay}" Grid.Row="2" Grid.Column="2" MinWidth="65" />
<controls:DatePicker SelectedDate="{Binding EndDate, Mode=TwoWay}" Grid.Row="2" Grid.Column="1" />
</Grid>
</Grid>
</DataTemplate>
I thought I could get rid of the first grid definition and just keep the one that is right before the content. The problem is, if I get rid of that grid, the textbox, data picker, and timepicker don't show up.
Am I missing some unwritten rule about the RowDetailsTemplate that is causing this voodoo behaviour?
You can possibly get rid of the third ColumnDefinition entry in the first grid as there is nothing assigned to it, the only purpose at the moment of column definitions two and three will be to divide the available width into three portions. If you do this though you may want to apply a width to the first column definition and leave the second column as auto sizing to fill the remaining space.
When you have items inside a grid, they are automatically placed in column 0, row 0, unless you specify otherwise. This means your inner grid is being placed in the first column (column 0) of the outer grid because it doesn't have a Grid.Column="" entry. This column is currently using 1/3 of the available width of its parent container. IOW, the outer grid is being used to constrain the inner grid.
You could try using Snoop to determine what is going on with your current and modified layouts, or you could add borders to the grids so you can see their exact positions once rendered.
When I expand the Expander in the code below, the application becomes very sluggish and takes 2-3 seconds to respond to resize/move events triggered by the user. If I set the second column to <ColumnDefinition Width="250"/> response time remains optimal. What am I missing here? (I'm need the UI as per below but without the sluggishness)
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Toolbar .. />
<DataGrid Grid.Row="1" ItemsSource="{Binding collection1}" .. />
<Expander Grid.Column="1" Grid.RowSpan="2">
<DataGrid ItemsSource="{Binding collection2}" .. />
</Expander>
</Grid>
</UserControl>
FYI: I suspect Row Virtualization is not being used when Width=Auto is set and DataGridRow objects are being created for the entire bound data source...
UPDATE
The following also does not remove the sluggishness;
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width={Binding ElementName=expander, Path=Width} />
</Grid.ColumnDefinitions>
..
<Expander Name="expander" Grid.Column="1" Grid.RowSpan="2">
<DataGrid ItemsSource="{Binding collection2}" Width="300" .. />
</Expander>
The problem is that if you set the ColumnWidth to Auto, the column width will be calculated depending on the content, here, the dataGrid. So you will have the second column very large (even though you might not see it depending on your layout) and the dataGrid's columns will all be drawn every time.
So basically, you loose the benefits of columnVirtualization (not rowVirtualization).
This is the same reason why you should never put a DataGrid into a ScrollViewer.
Solution
Set VirtualizingStackPanel.IsVirtualizing="True" on your grid
if this does not work, you might have to take care of the resizing yourself, no real option there.
I am using a GridSplitter to resize a cell in a grid however its behaviour is not what I am expecting and I cannot find a solution. It is a grid of three rows, the first has a row definition set to Auto and contains some elements. The second row has some data in it and has a row definition of * to fill the remaining space. The last row is a status bar that needs to be resizable, and so has a grid splitter in it and a row definition height of Auto and MinHeight of 30.
The problem is when you drag the GridSplitter all the way to the top, it will make the cell overflow. I wish for it to STOP once it gets to the top. The desired behaviour can be achieved by removing the Height=Auto from the last row, but that makes the bottom cell expand to equal height with the middle row.
Here is a XAML Pad example.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="20" />
<RowDefinition Height="Auto" MinHeight="30" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Foo" />
<TextBlock Grid.Row="1" Text="Bar" />
<GridSplitter Canvas.ZIndex="1" VerticalAlignment="Top" Grid.Row="2" Background="Cyan" Height="5" HorizontalAlignment="Stretch" />
<TextBlock VerticalAlignment="Bottom" Grid.Row="2" TextWrapping="Wrap">LOL<LineBreak/>LOL<LineBreak/>LOL</TextBlock>
</Grid>
</Page>
When you drag to the top, you will notice the bottom text disappears.
I have tried various things, such as putting the grid splitter in its own cell, and Binding Height to another objects ActualHeight etc but none really work that well.
I know it isn't the most well explained question, but any ideas would be greatly appreciated.
Edit:
I have made the GridSplitter with its own row as posted below, but as I mentioned earlier the problem still remains. I have the ResizeBehavior and ResizeDirection also set here.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="20" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" MinHeight="30" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Foo" />
<TextBlock Grid.Row="1" Text="Bar" />
<GridSplitter ResizeDirection="Rows" ResizeBehavior="PreviousAndNext" Grid.Row="2" Background="Cyan" Height="5" HorizontalAlignment="Stretch" />
<TextBlock VerticalAlignment="Bottom" Grid.Row="3" TextWrapping="Wrap">LOL<LineBreak/>LOL<LineBreak/>LOL</TextBlock>
</Grid>
</Page>
An example of what does work is removing the last rows Height="Auto" and changing it to * like so
This however makes the last row equal in size to the row before it and not to the requested size of the cell.
GridSplitter should lie at its own row or column. Experiment with GridSplitter.ResizeDirection and GridSplitter.ResizeBehavior properties.
Take a look at the following articles:
How to: Resize Rows with a GridSplitter
How to: Resize Columns with a GridSplitter
UPDATE
You may provide "star coefficients" to GridLength object. For example:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="95*" MinHeight="20" /> <!--here we are using 95*-->
<RowDefinition Height="Auto" />
<RowDefinition Height="5*" MinHeight="30"/> <!--and here we are using 5*-->
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Foo" />
<TextBlock Grid.Row="1" Text="Bar" />
<GridSplitter ResizeDirection="Rows" Grid.Row="2" Background="Cyan" Height="5" HorizontalAlignment="Stretch" />
<TextBlock VerticalAlignment="Bottom" Grid.Row="3" TextWrapping="Wrap">LOL<LineBreak/>LOL<LineBreak/>LOL</TextBlock>
</Grid>
</Page>
So we have the layout as you need without GridSplitter unclear behaviour.
Drat, beat me to it. I might as well post what I have. Your issue is with the third row definition. When you start scrolling up and the text disappears, the row's height keeps increasing. You could try setting the max height to some restriction, if Eugene's solution doesn't work.