WPF ToolbarControl overflow amounts - c#

I'm writing an application that has multiple ToolbarControls. There are other UI elements between the toolbars, so I cannot use ToolbarManagers. Here is the UI Hierarchy:
<UserControl>
<DockPanel>
<StackPanel DockPanel.Dock="Top">
<ToolbarTray> <Toolbar/> <ToolbarTray/>
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<StackPanel DockPanel.Dock="Top">
<ToolbarTray> <Toolbar/> <ToolbarTray/>
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<!-- as the above toolbars expand in size, these two are pushed offscreen>
<StackPanel DockPanel.Dock="Bottom">
<Some Other UI Stuff, separators, text, etc/>
<StackPanel/>
<DockPanel/>
<UserControl/>
My question is, how can I make the Toolbar controls show as many buttons as possible? They overflow into a dropdown menu when full, but currently their MaxHeight values are all hard coded. I am looking to make this dynamic, so the maximum number of buttons appear depending on the size of the monitor. I've tried doing so by removing the height restrictions, but then the last StackPanel always just gets forced off screen.
Notice that the last StackPanel MUST reside on the bottom of the DockPanel, and contains no toolbar but only some other UI elements.

You should place the area with the highest priority first in your dockpanel.
You can test this with the following very primitive setup (place in a WPF window and resize height, observe in which order the controls are expanded / shrinked):
Your current way:
<DockPanel>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100"/>
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100"/>
<!-- Last one fills -->
<Rectangle Fill="Orange"/>
</DockPanel>
Let bottom keep its size and shrink top first
<DockPanel>
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100"/>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100"/>
<!-- Last one fills -->
<Rectangle Fill="Orange"/>
</DockPanel>
A typical problem with this approach is the tab-order. You can keep the screen visible tab order as follows:
<DockPanel KeyboardNavigation.TabNavigation="Local">
<Rectangle DockPanel.Dock="Bottom" Fill="Red" Height="100" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="3"/>
<Rectangle DockPanel.Dock="Top" Fill="Green" Height="100" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="1"/>
<!-- Last one fills -->
<Rectangle Fill="Orange" KeyboardNavigation.TabNavigation="Local" KeyboardNavigation.TabIndex="2"/>
</DockPanel>
I use the rectangles to make different areas and the priority of their resizing visible - you can place any of your controls in place of the rectangles.
Edit: regarding the comment mentioning fixed size and two relative sized areas (I took the freedom to ignore the 20%, 30% and only look at the ratio), a grid might be the better choice:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="Green"/>
<Rectangle Grid.Row="1" Fill="Orange"/>
<Rectangle Grid.Row="2" Fill="Red" Height="100"/>
</Grid>
Regarding rotation: one way would be to swap the row and column definitions... make sure to include all relevant properties in the swap.
<Grid x:Name="myGrid" Margin="0,0,0,40">
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Grid.Column="0" Fill="Green"/>
<Rectangle Grid.Row="1" Grid.Column="0" Fill="Orange"/>
<Rectangle Grid.Row="2" Grid.Column="0" Fill="Red" MinHeight="100" MinWidth="100"/>
</Grid>
<Button VerticalAlignment="Bottom" HorizontalAlignment="Left" Margin="10" Content="Change Rotation" Click="Button_Click"/>
Code behind to swap grid rows and columns:
private void Button_Click(object sender, RoutedEventArgs e)
{
var rd = myGrid.RowDefinitions.ToList();
var cd = myGrid.ColumnDefinitions.ToList();
myGrid.RowDefinitions.Clear();
myGrid.ColumnDefinitions.Clear();
foreach (var item in cd)
{
myGrid.RowDefinitions.Add(new RowDefinition() { Height = item.Width });
}
foreach (var item in rd)
{
myGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = item.Height });
}
foreach (UIElement child in myGrid.Children)
{
var r = child.GetValue(Grid.RowProperty);
child.SetValue(Grid.RowProperty, child.GetValue(Grid.ColumnProperty));
child.SetValue(Grid.ColumnProperty, r);
}
}
It would also be possible to define different data templates for original and rotated view. It all depends on the requirements and complexity of involved controls.
Another alternative idea: lets actually rotate the view including its content... that might help if the parent container is not rotated:
private bool isRotated = false;
private void Button_Click(object sender, RoutedEventArgs e)
{
if (!isRotated)
{
myGrid.LayoutTransform = new RotateTransform(90, myGrid.ActualWidth / 2, myGrid.ActualHeight / 2);
}
else
{
myGrid.LayoutTransform = Transform.Identity;
}
isRotated = !isRotated;
}

Related

Include an item in 2 different scrollviewers

I have a grid divided into 4 grids A, B, C & D. I need to enable a horizontal scrollviewer only for 2 grids A and B. However, I need to enable a vertical scrollviewer only for 2 grids B & D. How can grid B be included in both the first horizontal scrollviewer and the second vertical scrollviewer?
Thanks a lot in advance.
Edit: Maximizing the window always makes it positioned on the left upper part of the window as depicted in the image below:
Here's the code I added for making sure the maximized window is positioned away from the upper left region. However, nothing changes about this issue!
private void Window_StateChanged(object sender, EventArgs e)
{
if (this.WindowState == WindowState.Maximized)
{
// Left = System.Windows.SystemParameters.WorkArea.Width - Width;
Left = 200;
// Top = System.Windows.SystemParameters.WorkArea.Height - Height;
Top = 200;
}
}
Any hints for keeping the window centered in the maximizing mode? Thanks very much in advance.
I'm assuming you're not really interested in a UniformGrid, but rather looking for a way to have a vertical ScrollViewer inside an horizontal ScrollViewer.
This code gives you a 2x2 Grid :
A B
C D
The first element added is an horizontal ScrollViewer that span inside both A and C, this element is divided in two, the bottom part has a vertical ScrollViewer.
The elements in B and D are simple elements.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Column="0" Grid.Row="0" Grid.RowSpan="2" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="http://via.placeholder.com/1000x1000"/>
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<Image Source="http://via.placeholder.com/1000x1000"/>
</ScrollViewer>
</Grid>
</ScrollViewer>
<Image Grid.Column="1" Grid.Row="0" Source="http://via.placeholder.com/1000x1000"/>
<Image Grid.Column="1" Grid.Row="1" Source="http://via.placeholder.com/1000x1000"/>
</Grid>
EDIT
Here's a new code to suit the requirements in your edit.
XAML
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="A" Grid.Column="0" Grid.Row="0" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled" ScrollChanged="A_ScrollChanged">
<Image Stretch="None" Source="http://lorempixel.com/1000/1000/"/>
</ScrollViewer>
<ScrollViewer x:Name="B" Grid.Column="0" Grid.Row="1" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" ScrollChanged="B_ScrollChanged">
<Image Stretch="None" Source="http://lorempixel.com/1000/1000/"/>
</ScrollViewer>
<ScrollViewer x:Name="C" Grid.Column="1" Grid.Row="0" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible">
<Image Stretch="None" Source="http://lorempixel.com/1000/1000/"/>
</ScrollViewer>
<ScrollViewer x:Name="D" Grid.Column="1" Grid.Row="1" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible" ScrollChanged="D_ScrollChanged">
<Image Stretch="None" Source="http://lorempixel.com/1000/1000/"/>
</ScrollViewer>
</Grid>
Code behind
private void A_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
B.ScrollToHorizontalOffset(e.HorizontalOffset);
}
private void B_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
A.ScrollToHorizontalOffset(e.HorizontalOffset);
D.ScrollToVerticalOffset(e.VerticalOffset);
}
private void D_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
B.ScrollToVerticalOffset(e.VerticalOffset);
}

WPF XAML and MinWidth and MaxWidth

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 *.

WPF shrink other conent when Expander is Expanded

Whenever Expander is expanded I would like to shrink grid cell above containing ListBox, so that you can always access every ListItem(if the listbox's grid cell would not shrink, lowest part would be inacessible). To illustrate:
item item *scrollbar*
item -> item *scrollbar*
item expanderItems
expander expander
I found bunch of threads for resizable expander, but none mentioning resizing other content. The problem is, grid containing listbox in 1st row and expander in 2nd with 2nd row Height set to Auto do not resize itself when expander is expanded.
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
I managed to code an ugly workaround using Expanded/Collapsed events:
private void Expander_Expanded(object sender, System.Windows.RoutedEventArgs e)
{
//grid1.Height is named content of expander
this.LayoutRoot.RowDefinitions[1].Height = new GridLength(this.LayoutRoot.RowDefinitions[1].ActualHeight + grid1.Height, GridUnitType.Pixel);
this.LayoutRoot.RowDefinitions[0].Height = new GridLength(1, GridUnitType.Star);
}
What would be the proper way? Preferably with no code behind and more "automated".
EDIT: xaml
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Expander Header="Expander" Margin="0" Grid.Row="1" VerticalAlignment="Bottom" Height="23" Panel.ZIndex="1" ExpandDirection="Up" Grid.RowSpan="2" Expanded="Expander_Expanded" Collapsed="Expander_Collapsed">
<Grid x:Name="grid1" Background="#FF762CF7" Height="100" Margin="0,-100,0,0"/>
</Expander>
<ListBox Margin="0" Background="#19FFFFFF">
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
<Button Height="150" Width="100"/>
</ListBox>
<Grid Margin="0" Grid.Row="1" Background="#FFAEFFAE"/>
<Grid Margin="0" Background="#FFFFD8D8"/>
</Grid>
The main problem you have is setting the Height property of the Expander. It changes its height when you expand it. But if you are setting it, it has to respect the height you gave it and cannot properly expand.
Instead of setting the Height, I would set the MinHeight (or completely remove height constraints, depending on what you want to do).
Additionally you should remove the Margin of the grid inside the expander.

How to recalculate size when StackPanel Children changes?

I have a Grid with a StackPanel as header, something like this in XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel x:Name="HeaderPanel" Grid.Row="0">
<Rectangle Height="100" Fill="Red" />
</StackPanel>
<Rectangle Fill="Blue" Grid.Row="1" />
</Grid>
When I remove the red rectangle in C# code:
HeaderPanel.Children.Clear()
It disappears but it still takes up space. I expected the Auto height StackPanel should collapse to 0 and the blue rectangle to take all the space of the grid. Can I make the sizes to be recalculated?
Platform is Windows Phone 8.
Inspired by Chris W.s comment, this is how I solved it. I changed the XAML to something like:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Height="100" Fill="Red" x:Name="Header" Grid.Row="0"/>
<Rectangle Fill="Blue" Grid.Row="1" />
</Grid>
Then in code I do:
ContentPanel.Children.Remove(Header);
If I want to add an other header:
Rectangle r2 = new Rectangle();
r2.Fill = new SolidColorBrush(Colors.Green);
r2.SetValue(Grid.RowProperty, 0);
r2.Height = 50;
ContentPanel.Children.Add(r2);
I came across this in a Windows Phone Silverlight project. I really wanted to keep the StackPanels so I found a workaround with the Height property. I have a method that fills the stack panel with UIElements:
private void LayoutStackPanel(IEnumerable<UIElement> elements)
{
stackPanel.Children.Clear();
if (elements.Count == 0)
{
stackPanel.Height = 0;
}
else
{
// height must be removed or ui elements will overlap
stackPanel.ClearValue(StackPanel.HeightProperty);
foreach (var element in elements)
{
stackPanel.Children.Add(element);
}
}
}
I've only tested on Windows Phone but I think it would work on WPF as well.

Autosizing button child elements as button changes size

I have a number of buttons within a grid, that all change size based on the application being resized. Some buttons have child elements such as an ellipse or rectangle. I can't seem to get these to resize as well.
Here is some code:
<Grid DockPanel.Dock="Right" Width="121">
<Grid.RowDefinitions>
<RowDefinition Height="121*" />
<RowDefinition Height="121*" />
// Snip
</Grid.RowDefinitions>
<Button Name="ellipseButton" PreviewMouseDown="EllipseButtonClickedEvent" HorizontalAlignment="Stretch" Grid.Row="0">
<Ellipse Width="90" Height="90" Stroke="Black"></Ellipse>
</Button>
// Snip
</Grid>
I know that the height and width of the ellipse is explicitly set, but when I remove that, I can't seem to get the ellipse to show at all.
I would appreciate any advice.
Thanks.
Just set the HorizontalContentAlignment and VerticalContentAlignment properties of the button to Stretch:
<Button Name="ellipseButton" PreviewMouseDown="EllipseButtonClickedEvent" HorizontalAlignment="Stretch" Grid.Row="0"
HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" >
<Ellipse Stroke="Black" />
</Button>

Categories

Resources