I have a basic expander that contains one grid and one expander.
How can i modify width of expander content programmatically? I've searched a lot
but i didn't find anything usefull.
Here is my code:
<Expander Header="Test" ExpandDirection="Left" HorizontalAlignment="Right" Background="LightBlue" Collapsed="VerticalExpander_OnCollapsed" Expanded="VerticalExpander_OnExpanded">
<Grid x:Name="Grid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="2"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="2" Text="Lorem ipsum dolor sit"/>
<GridSplitter Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" ResizeDirection="Columns"/>
</Grid>
private void VerticalExpander_OnExpanded(object sender, RoutedEventArgs e)
{
this.Grid.ColumnDefinitions[0].Width = new GridLength(300, GridUnitType.Pixel);
this.Grid.ColumnDefinitions[2].Width = new GridLength(300, GridUnitType.Pixel);
}
private void VerticalExpander_OnCollapsed(object sender, RoutedEventArgs e)
{
}
Result:
When i expand i want to change content width programmatically.
Thanks in advance!
If you want to to move the GridSplitter to the left, you could increase the Width of the third column, e.g.:
this.Grid.ColumnDefinitions[2].Width = new GridLength(300, GridUnitType.Pixel);
Related
First of all I am working with MVVM / WPF / .Net Framework 4.6.1
I have a ListView configured with ItemsPanelTemplate in horizontal orientation that displays items from a DataTemplate. This setup allows me to fit as many items inside the Width of the ListView (the witdth size is the same from the Window), and behaves responsively when I resize the window.
So far everything is fine, now I just want to Identify what items are positioned on the first row, including when the window get resized and items inside the first row increase or decrease.
I merely want to accomplish this behavior because I would like to apply a different template style for those items (let's say a I bigger image or different text color).
Here below the XAML definition for the ListView:
<ListView x:Name="lv"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"></WrapPanel>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
BTW: I already did a work around where I am getting the Index from each ListViewItem and calculating against the Width of the Grid inside the DataTemplate that is a fixed value of 180, but unfortunately it did not work as I expected since I had to use a DependencyProperty to bind the ActualWidth of the of the ListView to my ViewModel and did not responded very well when I resized the window.
I know I am looking for a very particular behavior, but if anyone has any suggestions about how to deal with this I would really appreciate. Any thoughts are welcome even if you think I should be using a different control, please detail.
Thanks in advance!
You shouldn't handle the layout in any view model. If you didn't extend ListView consider to use an attached behavior (raw example):
ListBox.cs
public class ListBox : DependencyObject
{
#region IsAlternateFirstRowTemplateEnabled attached property
public static readonly DependencyProperty IsAlternateFirstRowTemplateEnabledProperty = DependencyProperty.RegisterAttached(
"IsAlternateFirstRowTemplateEnabled",
typeof(bool), typeof(ListView),
new PropertyMetadata(default(bool), ListBox.OnIsEnabledChanged));
public static void SetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty, value);
public static bool GetIsAlternateFirstRowTemplateEnabled(DependencyObject attachingElement) => (bool)attachingElement.GetValue(ListBox.IsAlternateFirstRowTemplateEnabledProperty);
#endregion
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is System.Windows.Controls.ListBox listBox))
{
return;
}
if ((bool)e.NewValue)
{
listBox.Loaded += ListBox.Initialize;
}
else
{
listBox.SizeChanged -= ListBox.OnListBoxSizeChanged;
}
}
private static void Initialize(object sender, RoutedEventArgs e)
{
var listBox = sender as System.Windows.Controls.ListBox;
listBox.Loaded -= ListBox.Initialize;
// Check if items panel is WrapPanel
if (!listBox.TryFindVisualChildElement(out WrapPanel panel))
{
return;
}
listBox.SizeChanged += ListBox.OnListBoxSizeChanged;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void OnListBoxSizeChanged(object sender, SizeChangedEventArgs e)
{
if (!e.WidthChanged)
{
return;
}
var listBox = sender as System.Windows.Controls.ListBox;
ListBox.ApplyFirstRowDataTemplate(listBox);
}
private static void ApplyFirstRowDataTemplate(System.Windows.Controls.ListBox listBox)
{
double calculatedFirstRowWidth = 0;
var firstRowDataTemplate = listBox.Resources["FirstRowDataTemplate"] as DataTemplate;
foreach (FrameworkElement itemContainer in listBox.ItemContainerGenerator.Items
.Select(listBox.ItemContainerGenerator.ContainerFromItem).Cast<FrameworkElement>())
{
calculatedFirstRowWidth += itemContainer.ActualWidth;
if (itemContainer.TryFindVisualChildElement(out ContentPresenter contentPresenter))
{
if (calculatedFirstRowWidth > listBox.ActualWidth - listBox.Padding.Right - listBox.Padding.Left)
{
if (contentPresenter.ContentTemplate == firstRowDataTemplate)
{
// Restore the default template of previous first row items
contentPresenter.ContentTemplate = listBox.ItemTemplate;
continue;
}
break;
}
contentPresenter.ContentTemplate = firstRowDataTemplate;
}
}
}
}
Helper Extension Method
/// <summary>
/// Traverses the visual tree towards the leafs until an element with a matching element type is found.
/// </summary>
/// <typeparam name="TChild">The type the visual child must match.</typeparam>
/// <param name="parent"></param>
/// <param name="resultElement"></param>
/// <returns></returns>
public static bool TryFindVisualChildElement<TChild>(this DependencyObject parent, out TChild resultElement)
where TChild : DependencyObject
{
resultElement = null;
if (parent is Popup popup)
{
parent = popup.Child;
if (parent == null)
{
return false;
}
}
for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
{
DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);
if (childElement is TChild child)
{
resultElement = child;
return true;
}
if (childElement.TryFindVisualChildElement(out resultElement))
{
return true;
}
}
return false;
}
Usage
<ListView x:Name="lv"
ListBox.IsAlternateFirstRowTemplateEnabled="True"
ItemsSource="{Binding Path = ItemsSource}"
SelectedItem="{Binding Path = SelectedItem}">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.Resources>
<DataTemplate x:Key="FirstRowDataTemplate">
<!-- Draw a red border around first row items -->
<Border BorderThickness="2" BorderBrush="Red">
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</Border>
</DataTemplate>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Grid Width="180" Height="35">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Grid.Row="0" Height="32" Width="32"
VerticalAlignment="Top" HorizontalAlignment="Left">
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding IconPathName}" />
</Ellipse.Fill>
</Ellipse>
<TextBlock Grid.Column="1" Grid.Row="0" TextWrapping="WrapWithOverflow"
HorizontalAlignment="Left" VerticalAlignment="Top"
Text="{Binding Name}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Remarks
If the visual tree itself will not change for the first row, consider to add a second attached property to the ListBox class (e.g., IsFirstRowItem) which you would set on the ListBoxItems. You can then use a DataTrigger to modify the control properties to change the appearance. This will very likely increase the performance too.
In my WPF app, I have four separate quadrants, each with it's own grid and data. The four grids are separated by GridSplitters. The GridSplitters allow the user to resize each box by selecting either a horizontal or vertical splitter.
I am trying to allow the user to resize the grids by selecting the center point (circled in red).
I expected to have a four-way mouse pointer that could be used to drag up, down, left, and right. But, I only have the option to move windows up and down... or left and right.
What I've tried:
<Grid> <!-- Main Grid that holds A, B, C, and D -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="gridA" Grid.Column="0" Grid.Row="0"/>
<GridSplitter Grid.Column="0" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridC" Grid.Column="2" Grid.Row="0"/>
<GridSplitter Grid.Column="3" Grid.Row="1" Height="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridB" Grid.Column="0" Grid.Row="2"/>
<GridSplitter Grid.Column="1" Grid.Row="0" Width="5" HorizontalAlignment="Stretch"/>
<Grid x:Name="gridD" Grid.Column="2" Grid.Row="2"/>
<GridSplitter Grid.Column="1" Grid.Row="2" Width="5" HorizontalAlignment="Stretch"/>
</Grid>
Let me begin by changing your XAML a little bit, since right now we have four distinct GridSplitters, but two is enough:
<Grid Name="SplitGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="GridA" Grid.Column="0" Grid.Row="0" Background="Red" />
<Grid x:Name="GridC" Grid.Column="2" Grid.Row="0" Background="Orange" />
<Grid x:Name="GridB" Grid.Column="0" Grid.Row="2" Background="Green" />
<Grid x:Name="GridD" Grid.Column="2" Grid.Row="2" Background="Yellow" />
<GridSplitter x:Name="VerticalSplitter"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Width="5"
Background="Black" />
<GridSplitter x:Name="HorizontalSplitter"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="3"
Height="5"
HorizontalAlignment="Stretch"
Background="Black" />
</Grid>
What is more important about this markup is that we now have an intersection point between two splitters:
In order to drag two splitters at a time, we need to know when should we. For that purpose, let's define a Boolean flag:
public partial class View : Window
{
private bool _mouseIsDownOnBothSplitters;
}
We need to update the flag whenever the user clicks on either of the splitters (note that Preview events are used - GridSplitter implementation marks Mouse events as Handled):
void UpdateMouseStatusOnSplittersHandler(object sender, MouseButtonEventArgs e)
{
UpdateMouseStatusOnSplitters(e);
}
VerticalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonDown += UpdateMouseStatusOnSplittersHandler;
VerticalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;
HorizontalSplitter.PreviewMouseLeftButtonUp += UpdateMouseStatusOnSplittersHandler;
The UpdateMouseStatusOnSplitters is the core method here. WPF does not provide multiple layer hit testing "out of the box", so we'll have to do a custom one:
private void UpdateMouseStatusOnSplitters(MouseButtonEventArgs e)
{
bool horizontalSplitterWasHit = false;
bool verticalSplitterWasHit = false;
HitTestResultBehavior HitTestAllElements(HitTestResult hitTestResult)
{
return HitTestResultBehavior.Continue;
}
//We determine whether we hit our splitters in a filter function because only it tests the visual tree
//HitTestAllElements apparently only tests the logical tree
HitTestFilterBehavior IgnoreNonGridSplitters(DependencyObject hitObject)
{
if (hitObject == SplitGrid)
{
return HitTestFilterBehavior.Continue;
}
if (hitObject is GridSplitter)
{
if (hitObject == HorizontalSplitter)
{
horizontalSplitterWasHit = true;
return HitTestFilterBehavior.ContinueSkipChildren;
}
if (hitObject == VerticalSplitter)
{
verticalSplitterWasHit = true;
return HitTestFilterBehavior.ContinueSkipChildren;
}
}
return HitTestFilterBehavior.ContinueSkipSelfAndChildren;
}
VisualTreeHelper.HitTest(SplitGrid, IgnoreNonGridSplitters, HitTestAllElements, new PointHitTestParameters(e.GetPosition(SplitGrid)));
_mouseIsDownOnBothSplitters = horizontalSplitterWasHit && verticalSplitterWasHit;
}
Now we can implement the concurrent dragging. This will be done via a handler for DragDelta. However, there are a few caveats:
We only need to implement the handler for the splitter that is on top (in my case that'll be the HorizontalSplitter)
The Change value in DragDeltaEventArgs is bugged, the _lastHorizontalSplitterHorizontalDragChange is a workaround
To actually "drag" the other splitter, we'll have to change the dimensions of our Column/RowDefinitions. In order to avoid weird clipping behavior (the splitter dragging the column/row with it), we'll have to use the size of it in pixels as the the size of it in stars
So, with that out of the way, here's the relevant handler:
private void HorizontalSplitter_DragDelta(object sender, DragDeltaEventArgs e)
{
if (_mouseIsDownOnBothSplitters)
{
var firstColumn = SplitGrid.ColumnDefinitions[0];
var thirdColumn = SplitGrid.ColumnDefinitions[2];
var horizontalOffset = e.HorizontalChange - _lastHorizontalSplitterHorizontalDragChange;
var maximumColumnWidth = firstColumn.ActualWidth + thirdColumn.ActualWidth;
var newProposedFirstColumnWidth = firstColumn.ActualWidth + horizontalOffset;
var newProposedThirdColumnWidth = thirdColumn.ActualWidth - horizontalOffset;
var newActualFirstColumnWidth = newProposedFirstColumnWidth < 0 ? 0 : newProposedFirstColumnWidth;
var newActualThirdColumnWidth = newProposedThirdColumnWidth < 0 ? 0 : newProposedThirdColumnWidth;
firstColumn.Width = new GridLength(newActualFirstColumnWidth, GridUnitType.Star);
thirdColumn.Width = new GridLength(newActualThirdColumnWidth, GridUnitType.Star);
_lastHorizontalSplitterHorizontalDragChange = e.HorizontalChange;
}
}
Now, this is almost a full solution. It, however, suffers from the fact that even if you move your mouse horizontally outside of the grid, the VerticalSplitter still moves with it, which is inconsistent with the default behavior. In order to counteract this, let's add this check to the handler's code:
if (_mouseIsDownOnBothSplitters)
{
var mousePositionRelativeToGrid = Mouse.GetPosition(SplitGrid);
if (mousePositionRelativeToGrid.X > 0 && mousePositionRelativeToGrid.X < SplitGrid.ActualWidth)
{
//The rest of the handler's code
}
}
Finally, we need to reset our _lastHorizontalSplitterHorizontalDragChange to zero when the dragging is over:
HorizontalSplitter.DragCompleted += (o, e) => _lastHorizontalSplitterHorizontalDragChange = 0;
I hope it is not too daring of me to leave the implementation of the cursor's image change to you.
I have a Grid with a lot of cells, some of them are empty.I want to determine on which cell was the mouse, when the MouseDown event happened.How is that possible?
The first thing to remember is that a control with a transparent background doesn't generate events for its transparent regions. Either set a color for the grid you want, or bind it to the background color of the Window the grid is in, or the events will not fire.
This code sample demonstrates a computational method for determining grid element location given a MouseMove event. The ButtonClick event arguments are very similar. The relevant methods from this sample are ColumnComputation and RowComputation, which take the position on the control and the column or row definitions, for a linear analysis. The sample operates on the InnerGrid UI Element.
Form Class:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void InnerGrid_PreviewMouseMove(object sender, MouseEventArgs e)
{
this.XCoordinate.Text = e.GetPosition(InnerGrid).X.ToString();
this.YCoordinate.Text = e.GetPosition(InnerGrid).Y.ToString();
this.ColumnPosition.Text = ColumnComputation(InnerGrid.ColumnDefinitions, e.GetPosition(InnerGrid).X).ToString();
this.RowPosition.Text = RowComputation(InnerGrid.RowDefinitions, e.GetPosition(InnerGrid).Y).ToString();
}
private double ColumnComputation(ColumnDefinitionCollection c, double YPosition)
{
var columnLeft = 0.0; var columnCount = 0;
foreach (ColumnDefinition cd in c)
{
double actWidth = cd.ActualWidth;
if (YPosition >= columnLeft && YPosition < (actWidth + columnLeft)) return columnCount;
columnCount++;
columnLeft += cd.ActualWidth;
}
return (c.Count + 1);
}
private double RowComputation(RowDefinitionCollection r, double XPosition)
{
var rowTop = 0.0; var rowCount = 0;
foreach (RowDefinition rd in r)
{
double actHeight = rd.ActualHeight;
if (XPosition >= rowTop && XPosition < (actHeight + rowTop)) return rowCount;
rowCount++;
rowTop += rd.ActualHeight;
}
return (r.Count + 1);
}
}
XAML Form:
<Window x:Name="window" x:Class="GridHitTest.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 Name="OuterBorder" >
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" HorizontalAlignment="Center" >X Coordinate</TextBlock>
<TextBlock Grid.Column="1" HorizontalAlignment="Center" >Y Coordinate</TextBlock>
<TextBlock Grid.Column="2" HorizontalAlignment="Center" >Column</TextBlock>
<TextBlock Grid.Column="3" HorizontalAlignment="Center" >Row</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Center" Name="XCoordinate">kjahsd</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center" Name="YCoordinate">___ahsdjf</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="2" HorizontalAlignment="Center" Name="ColumnPosition">___ahsdjf</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="3" HorizontalAlignment="Center" Name="RowPosition">___ahsdjf</TextBlock>
<Grid Name="InnerGrid" Margin="20,45,20,10" Grid.ColumnSpan="4" Grid.RowSpan="3" Background="{Binding Background, ElementName=window}" PreviewMouseMove="InnerGrid_PreviewMouseMove" >
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
</Grid>
</Window>
Place a button into the empty cells (or buttons in all cells, but style hidden when other items are present and make the cell not empty). Then when the user clicks on the cell, report the cell such as
private void OnButtonClick(object sender, RoutedEventArgs e)
{
var buttonClicked = sender as Button;
var gridRow = (int)buttonClicked.GetValue( MyGrid.RowProperty );
var gridColumn = (int)buttonClicked.GetValue( MyGrid.ColumnProperty );
}
Here is another option that seems much simpler than the suggested correct answer.
private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
var element = (UIElement)e.Source;
int row = Grid.GetRow(element);
int column = Grid.GetColumn(element);
}
I'm trying to hide first 2 columns of a Grid when a Button is clicked. My Grid layout has 3 columns, one with a grid, the second with a grid splitter and the third one with another Grid which has the Button.
When I run my program with the below code, it collapses the first 2 columns on click of the Button properly as expected and resizes the third grid, however the moment I resize the grid using the splitter, this does not work anymore. It hides the columns, however the third column is not resized to fill the Window. I want the first 2 columns to be collapsed and the third column to fill the whole area of the window(which happens if I do not resize using the splitter).
The xaml is as below:
<Grid>
<ColumnDefinition Width="Auto" x:Name="column1"/>
<ColumnDefinition Width="Auto" x:Name="column2"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" x:Name="left" MinWidth="100">
<Border Background="Red" Margin="5"/>
<TextBlock Text="A Brown fox jumped oversomething" Width="{Binding ActualWidth, ElementName=TreeView}" Margin="5"></TextBlock>
</Grid>
<GridSplitter x:Name="splitter"
Width="5"
Grid.Column="1"
HorizontalAlignment="Left"
Margin="0,5,0,5"
Panel.ZIndex="1"
VerticalAlignment="Stretch"
ResizeDirection="Columns"/>
<Grid Grid.Column="2">
<Grid Grid.Column="0" Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Green" Margin="5"/>
<Button Grid.Row="1" Click="OnClick">HideAndResize</Button>
</Grid>
</Grid>
</Grid>
and the Button.click event is handled as below:
private bool clicked;
private void OnClick(object sender, RoutedEventArgs e)
{
clicked = !clicked;
left.Visibility = clicked ? Visibility.Collapsed : Visibility.Visible;
splitter.Visibility = clicked ? Visibility.Collapsed : Visibility.Visible;
}
Seems like the Column isnt autosizing correctly, so its still not 0, even if it's childs Visibility is set to Collapsed.
A quick and dirty solution would be:
private bool clicked;
private double oldLenght;
private void OnClick(object sender, RoutedEventArgs e)
{
clicked = !clicked;
splitter.Visibility = clicked ? Visibility.Collapsed : Visibility.Visible;
left.Visibility = clicked ? Visibility.Collapsed : Visibility.Visible;
oldLenght = clicked ? column1.ActualWidth : oldLenght;
column1.Width = clicked ? new GridLength(0.0) : new GridLength(oldLenght);
}
I have 2 Silverlight UserControls that need to be placed side by side in a container. Any of them is present (visible) optionally and in case one is missing I want the remaining one to take 100% width of the container. Basically there are 4 possible states: if none of the 2 controls is present the container is collapsed. The other are like
this
The furthest I got so far is using a Grid with two columns
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<userControls:LeftControl Grid.Column="0" ...></userControls:LeftControl>
<userControls:RightControl Grid.Column="1" ...></userControls:RightControl>
</Grid>
This works fine except the case the right control is missing. The left then doesn't stretch.
I don't think Silverlight layout system is capable of figuring this out on its own.
See if something like this gets you closer to your answer.
Markup:
<Grid x:Name="LayoutRoot" Background="White">
<StackPanel Orientation="Vertical" Width="400" Height="200" Background="Fuchsia">
<Grid x:Name="ContainerGrid" Height="100" Background="Green">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="LeftControl" Grid.Column="0" Background="Red" LayoutUpdated="LeftControl_LayoutUpdated"></Border>
<Border x:Name="RightControl" Grid.Column="1" Background="Blue" LayoutUpdated="RightControl_LayoutUpdated"></Border>
</Grid>
<Button x:Name="ToggleLeft" Content="Toggle Left" Click="ToggleLeft_Click"></Button>
<Button x:Name="ToggleRight" Content="Toggle Right" Click="ToggleRight_Click"></Button>
</StackPanel>
</Grid>
Code behind:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void ToggleLeft_Click(object sender, RoutedEventArgs e)
{
LeftControl.Visibility =
LeftControl.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
private void ToggleRight_Click(object sender, RoutedEventArgs e)
{
RightControl.Visibility =
RightControl.Visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
private void LeftControl_LayoutUpdated(object sender, EventArgs e)
{
ContainerGrid.ColumnDefinitions[0].Width =
ContainerGrid.Children.Any(
x => (int)x.GetValue(Grid.ColumnProperty) == 0 && x.Visibility == Visibility.Visible)
? new GridLength(1, GridUnitType.Star)
: GridLength.Auto;
}
private void RightControl_LayoutUpdated(object sender, EventArgs e)
{
ContainerGrid.ColumnDefinitions[1].Width =
ContainerGrid.Children.Any(
x => (int)x.GetValue(Grid.ColumnProperty) == 1 && x.Visibility == Visibility.Visible)
? new GridLength(1, GridUnitType.Star)
: GridLength.Auto;
}
}