Zooming in specific part of screen in WPF - 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.

Related

WPF control alignment is wrong when dynamically added

Hi I thought I could solve this easily but it is driving me crazy.
I am using a UserControl to house a video player control based on VLC, along with play and stop buttons etc. I then place the UserControl on my main form. if the UserControl is declared in XAML it behaves normally.
I decided to rewrite the code to instantiate my UserControl dynamically, in case I need to destroy it and create another on the fly. But when I do the video moves to the top of its container instead of the middle.
The UserControl relevant section is here:
<Grid x:Name="LayoutParent" >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="12" />
</Grid.RowDefinitions>
<!-- I comment this if adding player dynamically -->
<!--<wpf:VlcPlayer Grid.Row="0" x:Name="Player" />-->
<!-- I un-comment this if adding player dynamically -->
<Grid x:Name="VideoPlayerPanel" Grid.Row="0" Margin="0" />
<StackPanel Grid.Row="1" Opacity="0.8">
...(buttons etc)
</StackPanel>
<ProgressBar ...(progressBar etc) />
</Grid>
My codebehind looks like this:
Dim Player As VlcPlayer = New VlcPlayer ' uncomment If adding the player dynamically
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Player.SetValue(Canvas.ZIndexProperty, -1)
VideoPlayerPanel.Children.Add(Player)
VolumeSlider.Value = 50
End Sub
I have tried VerticalAlignment="Center" and VerticalAlignment="Stretch" in XAML on the VideoPlayerPanel, with Center the video disappears entirely, with Stretch it still aligns to the top.
Any thoughts as to what I might do to align this centrally would be much appreciated!
When adding Player dynamiccaly you have different result, because you wrap Play in additional Grid. Try to add Player directly to first row of LayoutParent:
Player.SetValue(Grid.Row, 0)
LayoutParent.Children.Add(Player)
Thanks to all that replied.
I did some more research, I substituted in a Rectangle for the player control and it aligned perfectly. That led me to discover that the third party control was itself at fault. I had to get the source and change the VerticalAlignment directly.
Sorry for the runaround.
Remove Height="*" from first Row . * is used to occupy remaining space, so it is good to use it for the last Row.
Use fixed width and or Auto.

WPF adding stackpanel with border to grid gives error

I'm working on a nested grid/stackpanel/grid window in WPF. Its a Calander, with a maingrid cell being a day. In this day there's a textbox and a stackpanel. The stackpanel contains a grid. EVerything is done in c#, build at run time, because the layout changes with the current month/year. No major problems sofar except when I want to add a border to a stackpanel. It gives the following error:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: 'The invocation of the constructor on type 'ADBF.ToezAcad.Admin.OpleidingKalender.MainWindow' that matches the specified binding constraints threw an exception.' Line number '3' and line position '9'.
Nothing special at this position.
Funny thing is it only gives this error, the moment I add the stackpanel ( with border ) to the containing grid.
short version of the code:
Border stackpanelborder = new Border();
this.Content = stackpanelborder;
StackPanel stackpanel = new StackPanel();
stackpanelborder.Child = stackpanel;
Grid.SetColumn(stackpanel, m);
Grid.SetRow(stackpanel, d + 1);
mainGrid.Children.Add(stackpanel); // if I uncomment this line, it throws the error.
Any help would be highly appreciated,
Arnold
#ADBF you are adding the StackPanel to two different child collections. Each UIElement should only have one parent in the visual tree, though a UIElement may have many children, depending on it's type.
I think you wanted to add stackpanelborder to the children of the mainGrid instead.
Edit:
Also you should be referencing stackpanelborder in the SetColumn/SetRow Methods not stackpanel.
The reason being since stackpanel is a child of stackpanelborder it will render inside the that UIElement. However stackpanelborder needs to be told where to insert in the grid, assuming you intend to add additional columns/rows later.
Basically your XAML document if you had one should look like this:
<Grid Name="mainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition .../>
<ColumnDefinition .../>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition .../>
<RowDefinition .../>
</Grid.RowDefinitions>
<Border Name="stackpanelborder" Grid.Row="0" Grid.Column="0" ...>
<StackPanel Name="stackpanel" .../>
</Border>
</Grid>

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.

How to toggle a WPF Grid column visibility

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.

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