How to toggle a WPF Grid column visibility - wpf

I'm having some trouble getting this to work in a WPF app I'm working on. Basically, what I'm after is something like the Task pane in an MMC:
The app has three columns in the main part of the display. I need a column on the right side which is resizable. I presume this means using a Grid with a GridSplitter but anything that works will do.
I want to be able to save the width of the right-side column when the app is closed and load it when the app is opened but this should be an initial size: the user should be able to resize it.
When I resize the window, I want the left- and right-side columns to stay the same size and the middle column to resize with the window width.
The left- and right-side columns need to have a minimum width. When I resize the right-side column I want the centre column to get smaller but not the left-side column.
I also want to be able to toggle the visibility of the right-side column with a toggle button which is outside the column and when it returns to visibility I want it to be the same width it was before.
I'm trying to do as much as possible in XAML and with binding.
And can I have it topped with cream, ice cream and chocolate chips, please? :-)

As I read your requirements, instead of thinking of a Grid, I think of a DockPanel.
<DockPanel>
<Grid Name="right"
DockPanel.Dock="Right" MinWidth="100" />
<Grid Name="Left"
DockPanel.Dock="Left" MinWidth="100" />
<Grid Name="middle" />
</DockPanel>
If you make a way to resize right, then middle will change as right is resized. If you resize the window, only middle will change. Storing and setting the Width of right is up to you, but shouldn't be hard.
As for allowing the user to resize right, that will a bit trickier, but I found this article that should help. This other article might help even more.
For the visibility of right, you can set its Visibility to Collapsed to hide it and restore it by setting it to Visible.
Note: The panels inside don't have to be Grids, but you will want to use some sort of Panel for each. Whatever you have inside your current Grid columns should work just fine.

I used a Grid with GridSplitters since this made it really easy to resize the middle column while maintaining the widths of the left and right columns.
XAML:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MainWindow"
Title="Main Window"
Width="640" Height="480">
<Grid>
<Grid.ColumnDefinitions>
<!-- Left column -->
<ColumnDefinition Width="200" MinWidth="100"/>
<!-- Left GridSplitter column -->
<ColumnDefinition Width="5"/>
<!-- Center column. A width of * means the column will fill
any remaining space. -->
<ColumnDefinition Width="*"/>
<!-- Right GridSplitter column -->
<ColumnDefinition x:Name="RightSplitterColumn" Width="5"/>
<!-- Right column -->
<ColumnDefinition x:Name="RightColumn" Width="200"
MinWidth="100"/>
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" HorizontalAlignment="Stretch" />
<GridSplitter Grid.Column="3" HorizontalAlignment="Stretch" />
<Button x:Name="ToggleButton" Grid.Column="2"
Content="Toggle Right Column" Width="150" Height="25"
Click="ToggleButton_Click" />
</Grid>
</Window>
Code-Behind
When hiding the right column, I just set the column width to 0 since grid columns don't have a visibility property.
public partial class MainWindow : Window
{
private double rightColumnWidth;
private double rightColumnMinWidth;
private bool rightColumnHidden;
public MainWindow()
{
this.InitializeComponent();
}
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
if (rightColumnHidden)
{
// Restore the widths.
RightColumn.MinWidth = rightColumnMinWidth;
RightColumn.Width = new GridLength(rightColumnWidth);
RightSplitterColumn.Width = new GridLength(5);
}
else
{
// Remember the user-set widths for the columns.
rightColumnWidth = RightColumn.Width.Value;
rightColumnMinWidth = RightColumn.MinWidth;
// Remember to set the minimum width to 0 before changing the actual
// width.
RightColumn.MinWidth = 0;
RightColumn.Width = new GridLength(0);
RightSplitterColumn.Width = new GridLength(0);
}
rightColumnHidden = !rightColumnHidden;
}
}
As for saving and restoring the column widths on startup, I would just store the width variables to a settings file and then apply them when your app is reopened.

Set the columndefinition Width to Auto and put a control inside that column and give Star for the other columns . Whenever you want to hide the column with content, set the control.Visibility=Collapsed and since column width is Auto, you wont see that column and the remaining columns will take the space.

3 years later you can find another approach on CodeProject.
http://www.codeproject.com/Articles/437237/WPF-Grid-Column-and-Row-Hiding
It adds a "Visible" property to custom Column definitions.

Related

Zooming in specific part of screen in WPF

I am implementing one small WPF application which has multiple rows and multiple columns. 0th row and 0th column contains a MediaElement and 1st row and 0th column contains a full screen button. When user clicks on full screen button I want to switch to a gird which has only two rows and one column. 0th row and 0th column will occupy most of the screen space having inside MediaElement and 1st row and 0th column will show a minimize button which will bring original UI back.
In traditional windows we were used to toggle visibility of a full screen panel hosting WindowsMedia player to achieve this behavior. How can I achieve this in WPF?
Adding my XAML code.
<Window x:Class="LearnEnglish.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<MediaElement LoadedBehavior="Manual"
Name="me"
Source="C:\Users\Pritam\Documents\Freecorder\Screen\Northern Ireland Scene 1 LearnEnglish British Council.wmv"
Volume="{Binding ElementName=txtVolume,Path=Text}"
Grid.ColumnSpan="2">
</MediaElement>
<Button Click="Button_Click"
Grid.Row="1"
Margin="4">Play</Button>
<Button Click="Button_Click"
Grid.Row="1"
Grid.Column="1"
Margin="4">Full Screen</Button>
<Button Click="Button_Click"
Grid.Row="1"
Grid.Column="1"
Margin="4"
Visibility="Hidden">Restore</Button>
</Grid>
</Window>
When user clicks on 'Full Screen' button I want my 'MediaElement' to occupy most of the scree space ( by hiding all other controls ) and leaving 'Restore' button in the bottom-right hand side of screen.
Regards,
Hemant
You can do that completely in XAML by using a ToggleButton and a trigger on its IsChecked property which sets the width / height of all columns / rows you don't want to see to 0. Use x:Name to name the elements you want to change, that will make it easier to write the Trigger.
In order to be able to access all controls, you should define the trigger on a parent control which contains all the other controls, e.g. in a UserControl, a panel, a DataTemplate or ControlTemplate. In order to access the properties on different controls, use their names for the TargetName property on the setters. There is also a corresponding SourceName property on Trigger itself, so you don't have to define the Trigger on the ToggleButton itself.
Most simply, you would use code to change the size of your columns. Something like this, wired to the click events of your buttons, would work:
First, name your Grid:
<Grid Name="MyGrid">
Then, wire your buttons:
<Button Click="Button_Click" Grid.Row="1" Margin="4">Play</Button>
<Button Name="FullScreenButton" Click="FullScreenButton_Click" Grid.Row="1" Grid.Column="1" Margin="4">Full Screen</Button>
<Button Name="RestoreButton" Click="RestoreButton_Click" Grid.Row="1" Grid.Column="1" Margin="4" Visibility="Hidden">Restore</Button>
And, use handlers to change the grid:
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
meVid.Play()
End Sub
Private Sub FullScreenButton_Click(sender As Object, e As RoutedEventArgs)
MyGrid.ColumnDefinitions.Item(2).Width = New GridLength(0)
MyGrid.ColumnDefinitions.Item(3).Width = New GridLength(0)
FullScreenButton.Visibility = Windows.Visibility.Hidden
RestoreButton.Visibility = Windows.Visibility.Visible
End Sub
Private Sub RestoreButton_Click(sender As Object, e As RoutedEventArgs)
MyGrid.ColumnDefinitions.Item(2).Width = New GridLength(2, GridUnitType.Star)
MyGrid.ColumnDefinitions.Item(3).Width = New GridLength(1, GridUnitType.Star)
FullScreenButton.Visibility = Windows.Visibility.Visible
RestoreButton.Visibility = Windows.Visibility.Hidden
End Sub
That code will effectively toggle the widths of the columns you want to hide, to zero, and restore them to what you had defined in the XAML. Because you want to dynamically vary your Grid element sizes, you will need to do this in code someplace.
You can, of course, define this behavior in style triggers, or wire your size elements to ViewModel bindings, but those are topics all by themselves which might not pertain to your architecture. Since you defined XAML with code-behind behavior on the buttons, I put the code in the code-behind; the point is, you vary the size of your Grid ColumnDefinition and RowDefinition elements to hide them.
This avoids problems with templates or triggering but also introduces "separation of concerns" issues which may make your stuff harder to maintain, if it's part of a complex project.

Autoscaling to parent object within a ScrollViewer?

I am having an autoscaling issue with my pages. The pages are displayed within a ScrollViewer, which usually contains a column on the ~33% right and a grid on the ~66% left.
I want to only autoscale the grid's Height (ie. independantly from the column at the right), so that it stretches according the size of the window, ergo, the object that contains the ScrollViewer and not the ScrollViewer itself.
So far, I have not managed to put this idea into practice... How could I possibly explain to my xaml code that I want my grid to be stretched in comparison to the ScrollViewer's "parent" rather than the ScrollViwer itself?
Here is a code example of what I'm trying to accomplish here, if it make my case any simpler:
<Grid x:Name="Parent" MinHeight="400" MaxHeight="700">
<ScrollViewer x:Name="Annoying ScrollViewer">
<Grid x:Name="Page Content" ScrollViewer.VerticalScrollBarVisibility="Auto">
<!-- IN ANOTHER USERCONTROL FAR, FAR AWAY... -->
<Grid x:Name="Inner Page Content">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="330" />
</Grid.ColumnDefinitions>
<Grid x:Name="Right column" Grid.Column="1"></Grid>
<Grid x:Name="Left Grid" Grid.Column="0" VerticalAlignment="StretchToParentUserControl? Please?"></Grid>
</Grid>
<!-- BACK TO THE PARENT USERCONTROL -->
</Grid>
</ScrollViewer>
</Grid>
EDIT: I solved my problem by using the Window size in code-behind directly through App.Current.Host.Content.ActualHeight and substracting the height of the parent files's contents (it is static, so I can just type the numbers). Still, I won't put this solution as an answer since it does not actually answer the original question.
And here is a little drawing of what my (reached) goal was as requested (uh... just switch the words "left" and "right" in the picture) :

How to Hittest Grid ColumnDefinition?

I am making kind of WPF Designer. I want to find out ColumnDefinition i have clicked on to delete it from grid control. I will take care of those children who "are in that ColumnDefinition".
Can i get it from sender argument of click event handler?
Now im checking if e.GetPosition is in range of ColumnDefinition.ActualWidth but i wonder if there is more beautiful solution.
From within your click event handler:
int columnIndex = Grid.GetColumn((UIElement)sender);
where sender if a direct grid's child.
Why do you need to capture a click on ColumnDefinition anyway? Is virtual, it does not have any actual body, it is only a hint for Grid on how you want to layout its content.
So you have to set handlers on content objects, not on ColumnDefinition.
If you really need to capture a click on the whole surface of a grid cell, you may try to place a white (or other color the same as background) Reactangle inside it and capture a click on it.
Some clarification on how WPF Grid works.
When you add some controls to the Grid, they all become its children.
<Grid>
<Button/>
<TextBox/>
<Label/>
</Grid>
And they all will be displayed not regarding how you have configured Column or RowDefinitions.
Column and RowDefinitions only tell Grid how you want to aling all the existing elements inside it, but they are not containers, they don't hold elements inside.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button/><!-- this is identical to Grid.Column="0"-->
<TextBox Grid.Column="1"/>
<Label Grid.Column="2"/>
</Grid>
In this example we have created three ColumnDefinitions, even from the grid XAML you can see, that controls are not inside definitions. They are used just like ruler guides to align content.
Then you set attached properties on the elements to tell the grid where you want to put your elements.
When grid begins layout, it will see, that there are three elements, and three ColumnDefinitions, and will try to positions elements as ColumnDefinitions says.
But if you remove or change ColumnDefinitions in the runtime, grid will just realign controls in a new way.
If you want to hide some elements, you have to hide them, not ColumnDefinition.

Have WPF Window resize automatically based on the remaining area in DockPanel

I have a scenerio where I want to dynamically render a custom form object. This form is similar to a WinForms form. It will display one of more button bars, buttons can only live within a button bar in our implementation, and numerous edit control that the user will use for data entry.
In rendering the form we have in a configuration file a row height and column width, both as a pertcentage of the application area. All forms share a common row height and column width. The actual device unit value of these are calculated on applicaiton initialization.
My issue is with sizing the form. For example, I have a form that is supposed to be 15 rows by 80 columns, let's say this translates to 500 units high and 800 units wide. This would be the form area, not including button bars. Right now I am calculating the window height and width in a FormPreviewManager. However, it is cumbersome as I have to give the total units, including the size of the button bar(s) and the windows borders, which may change as they have the option of including a title bar or not in the floating form window.
Within the rendering of the form I use a DockPanel to render the button bar(s) and claim the remaining space as my grid of rows and columns. I tried creatign the button bar(s), setting the remaining space to my FormGrid object, then specifically setting the Width and Height for the FormGrid but it doesn't change the size of the containing window.
How can I make it so that the floating window sizes automatically? I want to be able to draw the button bar(s) then say remaining space is 500 units high and 800 units wide, the window then adjust to whatever size it needs to hold this data, no more and no less.
Any help on this, or a possible other approach, would be great.
Update
Basically if this was my entire XAML definition for my window I would like to be able to set the width and height of UnusedContent and have the window resize accordingly.
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Height="50" />
<StackPanel DockPanel.Dock="Left" Width="50" />
<ContentControl x:Name="UnusedContent" />
</DockPanel>
</Grid>
If I set UnusedContent to 200 by 200 it would create a window with interior dimensions of 250 by 250, the content area plus the two stack panels.
You can try placing a control to fill up the remaining DockPanel space, and then binding to that control's ActualHeight and ActualWidth properties
<DockPanel>
<StackPanel DockPanel.Dock="Top" Height="50" />
<StackPanel DockPanel.Dock="Left" Width="100" />
<ContentControl x:Name="Spacer" />
</DockPanel>
<Grid Height="{Binding ElementName=Spacer, Path=ActualHeight}"
Width="{Binding ElementName=Spacer, Path=ActualWidth}" />

Reset Expander to default collapse behavior

I'm using an expander inside a Resizer (a ContentControl with a resize gripper), and it expands/collapses properly when the control initially comes up. Once I resize it, the Expander won't properly collapse, as documented below. I ran Snoop on my application, and I don't see any heights set on Expander or its constituents.
How would I go about convincing Expander to collapse properly again? Or modifying Resizer to not make Expander sad would work as well.
Expander documentation says:
"For an Expander to work correctly, do not specify a Height on the Expander control when the ExpandDirection property is set to Down or Up. Similarly, do not specify a Width on the Expander control when the ExpandDirection property is set to Left or Right. When you set a size on the Expander control in the direction that the expanded content is displayed, the area that is defined by the size parameter is displayed with a border around it. This area displays even when the window is collapsed. To set the size of the expanded window, set size dimensions on the content of the Expander control or the ScrollViewer that encloses the content."
I resolved the problem by moving the Resizer inside the Expander, but I've run into the Expander issue elsewhere, so would still like an answer if someone has it.
thanks
I haven't had a chance to mock up this particular issue since then, but I recently discovered that setting Height or Width to Double.NaN resets it to its default free-spirited behavior.
Ironically, this was from reading the code of the Resizer control I was using in the first place.
Answering this a bit late (2+ years), but, hey, better late than never, right?
Anyway, I ran into this exact problem and was able to solve it with some code-behind to save and reset column widths.
I have a 3 columned Grid, with some content in the first column, the GridSplitter in the second column, and the Expander in the third column. It looks like what is happening is that after the GridSplitter is moved the width of the column containing the Expander is altered from Auto to a fixed size. This causes the Expander to no longer collapse as expected.
So, I added a private variable and two event handlers:
private GridLength _columnWidth;
private void Expander_Expanded (object sender, RoutedEventArgs e)
{
// restore column fixed size saved in Collapse event
Column2.Width = _columnWidth;
}
private void Expander_Collapsed (object sender, RoutedEventArgs e)
{
// save current column width so we can restore when expander is expanded
_columnWidth = Column2.Width;
// reset column width to auto so the expander will collapse properly
Column2.Width = GridLength.Auto;
}
When the Expander is collapsed I save Column2's fixed width (which was altered from Auto auto-magically in the background somewhere) then reset the width to Auto.
Then, when the expander is expanded, I restore the column back to the fixed width so it expands to the same width it was before it was collapsed.
Here's the XAML for reference:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition x:Name="Column2" Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto">
<!-- some content goes here -->
</ScrollViewer>
<GridSplitter HorizontalAlignment="Right" VerticalAlignment="Stretch"
Grid.Column="1" ResizeBehavior="PreviousAndNext" Width="5"
Background="Black" />
<Expander Grid.Column="2" ExpandDirection="Left"
IsExpanded="True" Style="{StaticResource LeftExpander}"
Expanded="Expander_Expanded" Collapsed="Expander_Collapsed">
<Grid>
<TextBox TextWrapping="Wrap" Height="Auto" Margin="0 5 5 5" />
</Grid>
</Expander>
</Grid>

Resources