I have a Grid with two columns. I want to hide the second column until some one click on a Button in the first column. Problems is:
if I set visibility to collapsed on the second column, this doesn't display the element, and do not reserve space for it in layout causing the grid to not center when the second column is visible.
If I set the visibility to hidden, this doesn't display the element, but reserve space for the element in layout causing it to center but show the reserved space which I don't want when column 1 is visible and column 2 is not.
I want something in-between. For example, show only first column and center whole grid. When column two is also shown, center the whole grid again.
Does this make sense?
XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Label Content="Show second column on grid" Width="300"
FontStyle="Italic"
FontWeight="Bold"
Grid.Column="0"
Grid.Row="0"
HorizontalAlignment="center"
Foreground="White"/>
<Button Content="Click Me"
Width="75"
Margin="5"
Grid.Column="0"
Grid.Row="1" Click="Button_Click" />
<StackPanel x:Name="showSecondColumn"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
Margin="5,0,0,0"
Orientation="Vertical"
Background="Red" Visibility="Collapsed">
<Label Content="Second column is shown"
Background="Red" />
<Label Content="Another item inside second column"
BorderBrush="Red"
BorderThickness="3"
Margin="0,5"
Background="White" />
</StackPanel>
</Grid>
Code behind
private void Button_Click(object sender, RoutedEventArgs e)
{
showSecondColumn.Visibility = Visibility.Visible;
}
What about trying a different approach?
You can have two columns, one with width="*" (so that it occupies all the available space), and set the width for second column as width="Auto".
Then in the second column, you can place your control/container with visibility set as collapsed by default. And on the button click, you can toggle it as required.
Related
My goal is to save a GridSplitter position for later recall. The splitter is inside a Grid control that has three columns defined thusly:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding GridPanelWidth, Mode=TwoWay}" />
<ColumnDefinition Width="3" /> <!--splitter itself is in this column-->
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
The property GridPanelWidth is defined this way in the view model:
private GridLength _gridPanelWidth = new GridLength(1, GridUnitType.Star);
public GridLength GridPanelWidth
{
get { return _gridPanelWidth; }
set
{
if (_gridPanelWidth != value)
SetProperty(ref _gridPanelWidth, value, () => GridPanelWidth);
}
}
The problem I am having is that when the splitter is moved, the binding updates only the Double (Value) component of the bound property, but not the GridUnitType part of it.
Example: the property defaults to 1*. User drags the splitter and the value becomes 354*, instead of just 354. On restoring the value, then, it's huge (354 times, not 354 pixels).
Why is this happening, and what would you do about it?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding GridPanelWidth, Mode=TwoWay}" />
<ColumnDefinition Width="4" />
<!--splitter itself is in this column-->
<ColumnDefinition x:Name="RightColumn" Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
BorderBrush="Gray"
BorderThickness="1"
Grid.Column="0"
Grid.Row="0"
/>
<GridSplitter
Background="SteelBlue"
ResizeBehavior="PreviousAndNext"
ResizeDirection="Columns"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
ShowsPreview="False"
Grid.Column="1"
Grid.Row="0"
/>
<Border
BorderBrush="Gray"
BorderThickness="1"
Grid.Column="2"
Grid.Row="0"
/>
<StackPanel
Grid.Row="1"
Grid.ColumnSpan="3"
Grid.Column="0"
>
<TextBlock>
<Run>GridPanelWidth: </Run>
<Run Text="{Binding GridPanelWidth.Value, Mode=OneWay}" />
<Run Text="{Binding GridPanelWidth.GridUnitType, Mode=OneWay}" />
</TextBlock>
<TextBlock>
<Run>RightColumn.Width: </Run>
<Run Text="{Binding Width.Value, ElementName=RightColumn, Mode=OneWay}" />
<Run Text="{Binding Width.GridUnitType, ElementName=RightColumn, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Grid>
Screenshot 1:
Screenshot 2:
Screenshot 3:
Res ipsa loquitor, as far as I'm concerned, but just to be on the safe side:
Because the parent may be resized, the grid splitter changes the ratio between the two columns, while preserving the GridUnitType.Star for each one so that when the parent is resized, the ratio will naturally remain constant. This preserves the intent of the initial Width values in the XAML.
Width.Value for the left column turns out to be identical to the left Border's ActualWidth, and the same holds true for the right column. You'll have to grab both and save the ratio.
Update
I find Grid/GridSplitter a bit overengineered for everyday use when all I want is Yet Another Navigation Pane, so I recently wrote a SplitterControl that has two content properties and sets up the grid and splitter, with styling, in the template. I hadn't gotten around to making the split ratio persistent, so I did that just now.
What I did was rather painful because the control is configurable, and the code isn't all that great, but I can share if you're interested.
The business end is simple:
When a column resizes, set a flag to block recursion and
PaneRatio = _PART_ContentColumn.Width.Value / _PART_NavColumn.Width.Value;
When PaneRatio changes, if it wasn't set by the column size change handler
_PART_NavColumn.Width = new GridLength(1, GridUnitType.Star);
_PART_ContentColumn.Width = new GridLength(PaneRatio, GridUnitType.Star);
In practice, the navigator/content columns can be swapped, or they can be rows instead. Both of those are done by switching templates on a HeaderedContentControl that's a child of the split control template.
I am trying to have a canvas that takes all window size upon resizing, and a button over it, in the bottom left corner, but which resizes with the window(up to some max size, maybe), and the margin from the button to left and bottom resizes with the window as well.
I have tried to achieve it with a grid like this:
<Canvas Background = "LightGray" x:Name="PaintCanvas"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Height="auto" Width="auto">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="20*" />
<RowDefinition Height="62*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="87*" />
</Grid.ColumnDefinitions>
<Button x:Name="testBut" Content="TEST"
Grid.Column="1" Grid.Row="1"
MaxHeight="150" MaxWidth="50"
/>
</Grid>
</Canvas>
Edit: it seems that i have to fill the canvas with the grid.
So, here's how i handled it:
Added my grid(with predefined empty cells that serve as margins) as the only child of Window. Then added Canvas as child of grid, but specified it to span across all its rows and columns - filling whole grid(and Window):
<Canvas Background="WhiteSmoke" x:Name="PaintCanvas"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
Grid.RowSpan="3" Grid.ColumnSpan="3"/>
Finally,i put the button in needed cell
I have StackPanel in which I wrapped 7 Rectangles all varying widths.
My First rectangle in the Stack, I have a Min and Max width defined and one of the other Rectangles (see below x:Name="ToBeCollapsed") that is Collapsed by default, BUT is made visible in a certain condition in the C#.
My issue is that the first Rectangle does not stretch to the MAX width if the rectangle "ToBeCollasped" is collapsed. My thought was that the first rectangle would fill the space to the MAX of "755" if the collapsed rectangle was collapsed.
Am I wrong in my logic?
My layout is below:
<StackPanel x:Name="RectangleColumns" Width="1840" Orientation="Horizontal">
<Rectangle MinWidth="575" MaxWidth="755" />
<Rectangle Width="315"/>
<Rectangle Width="180" />
<Rectangle Width="180"/>
<!--If collapsed first rectangle should grow to 755. MinWidth + 180-->
<Rectangle x:Name="ToBeCollapsed" Width="180"/>
<Rectangle Width="220"/>
<Rectangle Width="190"/>
</StackPanel>
A grid with column definitions set properly will do the trick. I've tried this myself using some fill colours and smaller widths to demonstrate the point, that first column grows into the space when the fifth column collapses:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" x:Name="RectangleColumns" HorizontalAlignment="Stretch ">
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="200" MaxWidth="400"/>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition MaxWidth="50"/>
<ColumnDefinition MaxWidth="50"/>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="0" Fill="Aqua" MinWidth="200" MaxWidth="400" />
<Rectangle Grid.Column="1" Fill="Blue" Width="50"/>
<Rectangle Grid.Column="2" Fill="Aqua" Width="50" />
<Rectangle Grid.Column="3" Fill="Blue" Width="50"/>
<Rectangle Grid.Column="4" Fill="Pink" Width="300" Name="toBeCollapsed"/>
<Rectangle Grid.Column="5" Fill="Aqua" Width="50"/>
<Rectangle Grid.Column="6" Fill="Blue" Width="50"/>
</Grid>
<Button Grid.Row="1" Content="Toggle Visibility" Name="ButtonToggle" />
</Grid>
Code-behind, just to demonstrate:
public MainWindow()
{
InitializeComponent();
ButtonToggle.Click += ButtonToggleOnClick;
}
private void ButtonToggleOnClick(object sender, RoutedEventArgs routedEventArgs)
{
toBeCollapsed.Visibility = toBeCollapsed.Visibility == Visibility.Collapsed
? Visibility.Visible
: Visibility.Collapsed;
}
Always look behind the schene:
In WPF, every LayoutControl is a Grid - or reproducable with a Grid - but it uses different Column or Row definitions and Alignment.
Stackpanel is a Grid which contains following setup (Horizontal):
Columndefinition for every added controls with Auto size
All added Child controls HorizontalAligment is Center
Additional Columndefinition with * Width
And that's the way the cookies cramble.
Auto sizing distributes space based on the size of the content that is within a column or row.
Which is usually the smallest possible size :/
So +1 for #Bradley Uffner
Use a Grid where Columndefinition of Min/Max Rectangle is *.
I have defined two columns with separator like this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="6"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<ScrollViewer Name="sideBar" Margin="{StaticResource SplitLeft}" SizeChanged="ScrollViewer_SizeChanged">
<StackPanel>
<TextBlock Text="LEFT CONTENT" Style="{StaticResource Heading2}" />
<TextBlock Text="" Name="ShowThings"/>
</StackPanel>
</ScrollViewer>
<GridSplitter Grid.Column="1" />
<ScrollViewer Name="ListPage" Grid.Column="2 " Margin="{StaticResource SplitRight}" SizeChanged="ScrollViewer_SizeChanged">
<StackPanel>
<TextBlock Text="RIGHT CONTENT" Style="{StaticResource Heading2}" />
<TextBlock Text="Content goes here" />
</StackPanel>
</ScrollViewer>
so the page is divided in 1:2 proportions. but if user wants to resize it, he can. I want to persist the modified width in app settings to be used for next launch.
on size change event I am storing the new width in settings,
Properties.Settings.Default[((ScrollViewer)sender).Name + "Width"] = e.NewSize.Width;
Properties.Settings.Default.Save();
and in window constructor loading the settings and setting the values.
sideBar.Width = (double)Properties.Settings.Default["sideBarWidth"];
but this does not work. can anyone help me identify the problem? I am sure this is a duplicate question, but I don't know what to search for.
It doesn't make sense to set the Width of your sideBar if it is depending on the size given in the Grid.ColumnDefinitions. Try to set the ColumnDefinition.Width:
// 0 = first column
grid.ColumnDefinitions[0].Width = new GridLength((double)Properties.Settings.Default["sideBarWidth"]);
I have a situation where I am trying to set the tab order (tabindex) for controls that are loaded dynamically. The main XAML looks like this:
<ItemsControl DockPanel.Dock="Top" ItemsSource="{Binding ItemsDataSource}" Name="overlayItems" ItemTemplate="{StaticResource DetailsTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
For example purposes, assume the DetailsTemplate is something simple like this:
<DataTemplate x:Key="DetailsTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="150" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="22" />
<RowDefinition Height="22" />
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Padding="0">Field 1</Label>
<TextBox Grid.Column="1" Grid.Row="0" Name="field1TextBox" TabIndex="0" Text="{Binding Field1Value}"/>
<Label Grid.Column="0" Grid.Row="1" Padding="0">Field 2</Label>
<TextBox Grid.Column="1" Grid.Row="1" Name="field2TextBox" TabIndex="1" Text="{Binding Field2Value}"/>
<Label Grid.Column="0" Grid.Row="2" Padding="0">Field 3</Label>
<TextBox Grid.Column="1" Grid.Row="2" Name="field3TextBox" TabIndex="2" Text="{Binding Field3Value}"/>
</Grid>
</DataTemplate>
This XAML works just fine except for the resulting tab order.
Assuming that the ItemsDataSource is a collection of a class and contains 3 instances of that class, three sets of the DetailsTemplate data template are created. However the tab order does not change, every field1TextBox remains at TabIndex 0. This means, instead of tabbing from the first instances of field1TextBox, to field2TextBox, to field3TextBox, the tab goes from the first instance of field1TextBox to the second instance of field1TextBox then to the third instance of field1TextBox then to the first instance of field2TextBox, and so on. My question is, how do I get the tab order corrected where, say, the second instance of the data template would have its text boxes tab indexes updated to 3, 4 and 5 respectively?
You'll find the answer in the KeyboardNavigation.TabNavigation Attached Property page from MSDN. This property Gets or sets the logical tab navigation behavior for the children of the element that this property is set on.
There are several possible values in the KeyboardNavigationMode Enumeration used that affect the tabbing order in different ways, but you're after the Local value, which has the effect that Tab Indexes are considered on local subtree only inside this container and ... [Navigation leaves the containing element when an edge is reached].
<Grid KeyboardNavigation.TabNavigation="Local">
...
</Grid>