Constrain Grid row with Height=Auto to the visible area - c#

Consider the following XAML:
<Window x:Class="WpfApplication4.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="*" MinHeight="100" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox>
<ListBox.Items>
<ListBoxItem>a</ListBoxItem>
<!-- Another 11 items -->
</ListBox.Items>
</ListBox>
<ListBox Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.Items>
<ListBoxItem>1</ListBoxItem>
<!-- Another 23 items -->
</ListBox.Items>
</ListBox>
</Grid>
</Window>
The ListBox in the second row shows the vertical scrollbar as disabled and simply cuts off the content.
I want it to be constrained to the visible area of the window. How to achieve this?
Rational behind setting the height of the second grid row to Auto:
I want the second ListBox to display all its content without a scrollbar if there is enough space for it and the first ListBox should take the remaining space.

I don't think there's any way to do what you want in pure XAML - you have to set a specific height for one or other of the two listboxes, or set fixed proportions for them.
I think you could do what you want with a bit of code in the code behind. Give your RowDefinitions and Listboxes names, as follows, and subscribe to the GridSizedChanged event:
<Grid SizeChanged="GridSizeChanged">
<Grid.RowDefinitions>
<RowDefinition x:Name="row1"/>
<RowDefinition x:Name="row2"/>
</Grid.RowDefinitions>
<ListBox x:Name="lb1">
<ListBox.Items>
<ListBoxItem>a</ListBoxItem>
</ListBox.Items>
</ListBox>
<ListBox x:Name="lb2" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.Items>
<ListBoxItem>1</ListBoxItem>
<!-- Another 23 items -->
</ListBox.Items>
</ListBox>
</Grid>
Then handle the event in the following way:
private void GridSizeChanged(object sender, SizeChangedEventArgs e)
{
double newHeight = e.NewSize.Height;
int lb1ItemCount = lb1.Items.Count;
int lb2ItemCount = lb2.Items.Count;
row1.Height = new GridLength(newHeight * lb1ItemCount / (lb1ItemCount + lb2ItemCount));
row2.Height = new GridLength(newHeight * lb2ItemCount / (lb1ItemCount + lb2ItemCount));
}
This sets the size of the two listboxes to be proportional to the number of items they have inside them. If you want to set a minimum size of 100 for the first listbox, you'll have to do a bit more work to set that size first, then base the second size off the calculated value for the first size.
Edit:
I think I've written a version of GridSizeChanged that does exactly what you require. This version will set the height of lb2 to either the whole grid except the top 100px (if the desired listbox size is bigger than this), or just to its own desired size if that is smaller. The first listbox will then fill all remaining space, and will have a minimum height of 100px as you require, because we did not allow lb2 to fill the top 100px.
private void GridSizeChanged(object sender, SizeChangedEventArgs e)
{
lb2.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double lb2DesiredHeight = lb2.DesiredSize.Height;
double newHeight = e.NewSize.Height;
double lb2AvailableHeight = newHeight - 100;
double lb2ActualHeight = Math.Min(lb2DesiredHeight, lb2AvailableHeight);
row1.Height = new GridLength(newHeight - lb2ActualHeight);
row2.Height = new GridLength(lb2ActualHeight);
}

Related

What causes this StackPanel scrolling issue?

I have a Customer Pages which contains information of customer name. This page contains a StackPanel and inside this StackPanel there is a ScrollViewer and inside the ScrollViewer there is another StackPanel.
I am adding number of StackPanels dynamically. Inside the StackPanel I am adding TextBlock dynamically behind, which contains text. Then add InkPresenter to show a separation.
My problem is after adding every time when I try to use the page, it doesn't scroll. In fact if I swipe up the page goes down and come back.
XAML :
<StackPanel>
<ScrollViewer Margin="0,-20,0,0" Grid.RowSpan="2" >
<StackPanel Height="Auto" x:Name="pottilelist" >
</StackPanel>
</ScrollViewer>
</StackPanel>
StackPanel stk = new StackPanel();
stk.Name = g.id.ToString();
stk.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(Customer_Click);
TextBlock tbx = new TextBlock();
tbx.Text = g.customername;
tbx.Name = "A" + g.id.ToString();
tbx.FontSize = 36;
tbx.Tap += new EventHandler<System.Windows.Input.GestureEventArgs>(Customer_Click);
tbx.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
stk.Children.Add(tbx);
InkPresenter ink = new InkPresenter();
ink.Height = 4;
ink.Background = Brush3;
ink.Margin = new Thickness(0, 0, 0, 20);
stk.Children.Add(ink);
pottilelist.Children.Add(stk);
In one app it worked, but in another it doesn't.
Try to put the scrol viewer around the Layout Grid (main grid) that contains all page contents. I think this should solve the problem.
As long as you are using Pivot Template , just put it after the deceleration of that pivot item around the grid and it should works !
<phone:PivotItem Header="PivotItem">
<ScrollViewer>
<Grid x:Name="LayoutRoot" Background="#7F000000" >
It depends on where your stackpanel is placed inside the grid.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="100"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="1">
<ScrollViewer>
<StackPanel></StackPanel>
</ScrollViewer>
</StackPanel>
</Grid>
In above code scrollviewer will work as expected, because of the height.
If you have set the height of the position in its visual tree, then scrollviewer might not scroll.
I have solved the problem. This problem occurs when there is no height for a stackpanel or scrollviewer. Height Issue

How to Set listbox location in WPF [duplicate]

This question already has answers here:
C# WPF how to set location,width and height of the controls programmatically?
(6 answers)
Closed 8 years ago.
i want to set listbox location. In Winform i did this by using this code listbox.Location but in WPF there is no listbox.Location property.
Edit 1:
var rect = txtBox.GetRectFromCharacterIndex(txtBox.CaretIndex);
var point = rect.BottomRight;
lstBox1.Visibility = Visibility.Visible;
//Want to achieved this
//TextBox.Location = point;
I am creating something like Intellisense with listbox
You should probably read up on WPF layouts, however to you can use the ListBox.Margin to position the ListBox in a hardcoded location.
listbox.Margin = new Thickness( 25, 200, 0, 0 );
or in XAML
<ListBox Margin="25,200,0,0"/>
A ListBox's location is determined relative to the control it is contained in. To set a location within it's parent control you can use the HorizontalAlignment, VerticalAlignment and Margin Properties.
Here is an example:
<Window x:Class="WpfApplication14.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>
<ListBox HorizontalAlignment="Left" Height="100" Margin="197,105,0,0" VerticalAlignment="Top" Width="100"/>
</Grid>
</Window>
Note - all these properties are available programmatically as well.
Thanks,
Eric
It all depends on what the parent panel is to determine how to set the position of your listbox. You'll need to read more about Layouts in WPF. Let's look at 2 panels to get you started, Grid and Canvas.
<Grid>
<ListBox x:Name="lb" HorizontalAlignment="Left" VerticalAlignment="Top"/>
</Grid>
lb.Margin = new Thickness(10,10,0,0);
The example above sets the ListBox lb in the Grid at position (10,10).
<Canvas>
<ListBox x:Name="lb"/>
</Canvas>
Canvas.SetTop(lb, 10);
Canvas.SetLeft(lb, 10);
The example above does the same for lb in a Canvas.
As you can see, it depends on what type of panel that you put your listbox into to be able to set the position correctly.

Remember tab size where the tabs have been resized

I have a tab control in a window with several tabs on it.
By default the size of the window resizes around the TabControl which is sized to the contents of the TabItems. This is desired behaviour. When one tab is resized all the tabs become that size. Presumably this is because it is the window that is being resized. However the desired behaviour is for the window to size to the tab unless that tab has been resized. If that tab has been resized its size needs to be remembered. I have been unable to implement this correctly, usually it will resize the first time but when the tab is set to again and resized it appears that only the window resizes and not the tabs.
Additionally I am unable to hard code the sizes as the controls on the TabItems are dynamically created and will not always be the same size. Some of these are WPF and some are WinForms controls in a WindowsFormHost. There are about a dozen of these but just using 3 in my example code. One is on a scroll viewer.
<Window....
SizeToContent="WidthAndHeight" ResizeMode="CanResize">
<Grid>
<TabControl x:Name="tabControl" SizeChanged="tabControl_SizeChanged">
<TabItem x:Name="tabItem1" Selector.Selected="tabParams_Selected">
<Grid>
<ScrollViewer MaxHeight="1000">
<DynamicWpfcontrol/>
</ScrollViewer>
</Grid>
</TabItem>
<TabItem x:Name="tabItem2" Selector.Selected="tabRepresentations_Selected">
<Grid>
<WindowsFormsHost Margin="3">
<my:DynamicWinformControl AutoScroll="True" AutoSize="True" AutoSizeMode="GrowAndShrink"/>
</WindowsFormsHost>
</Grid>
</TabItem>
<TabItem x:Name="tabItem3" Selector.Selected="tabAttributes_Selected">
<Grid>
<WindowsFormsHost Margin="3">
<DynamicWinformControl AutoScroll="True" AutoSize="True" AutoSizeMode="GrowAndShrink"/>
</WindowsFormsHost>
</Grid>
</TabItem>
</TabControl>
</Grid>
private Size tab1Size;
private Size tab2Size;
private Size tab3Size;
private void tabControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (tabItem1.IsSelected)
tab1Size = e.NewSize;
else if (tabItem2.IsSelected)
tab2Size = e.NewSize;
else if (tabItem1.IsSelected)
tab3Size = e.NewSize;
}
EDIT - I have updated the tabItemSelected events to use Measure and UpdateLayout instead of setting the width and height. I understand that measure sets the desired size. This is now working for remembering the width but the height is still being set back to the height of the contents.
private void tabItem1_Selected(object sender, RoutedEventArgs e)
{
if (tab1Size != Size.Empty && !(tab1Size.Height == 0 && tab1Size.Width == 0))
{
tabControl.Measure(tab1Size);
tabControl.Arrange(new Rect(tab1Size));
}
this.SizeToContent = System.Windows.SizeToContent.WidthAndHeight;
}

How to align RibbonGroup to the right?

I use RibbonControlsLibrary. How to align one RibbonGroup to the right? It should be only one group in tab. All other groups should be aligned to the left.
You cannot align the RibbonGroup to the right. The Ribbon doesn't provide the ability to do this.
What you can do is to align page header items... but I don't know if it's enough for you:
DevExpress
I had the same issue, and I finally found something to do this :
I have 3 RibbonGroupBox. Groupe1 may be aligned on left, Groupe3 may be aligned on right. Groupe2 is just an empty RibbonGroupBox I inserted between Groupe1 and Groupe3.
Code XAML :
<Fluent:Ribbon DockPanel.Dock="Top" Title="{x:Static p:Resources.MiseEnBarre}" x:Name="mainRibbon">
<Fluent:RibbonTabItem x:Name="MainMenu" Header="{x:Static p:Resources.MainMenu}" SizeChanged="MainMenu_SizeChanged">
<Fluent:RibbonGroupBox x:Name="Groupe1">
<Fluent:Button x:Name="autoNest" SizeDefinition="Large" LargeIcon="img\image_bar_Nesting.png" Header="{x:Static p:Resources.MenuAutoNest}" Click="AutoNest_Click" />
<Fluent:Button x:Name="saveFile" SizeDefinition="Large" LargeIcon="img\image_save.png" Header="{x:Static p:Resources.MenuSauvegarder}" Click="Sauvegarder_Click" />
</Fluent:RibbonGroupBox>
<Fluent:RibbonGroupBox x:Name="Groupe2">
</Fluent:RibbonGroupBox>
<Fluent:RibbonGroupBox x:Name="Groupe3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=AvailableCNClist}" HorizontalAlignment="Left"/>
<TextBlock Grid.Column="1" Text="{Binding Path=AvailableCNClist2}" HorizontalAlignment="Right"/>
</Grid>
</Fluent:RibbonGroupBox>
</Fluent:RibbonTabItem>
</Fluent:Ribbon>
Then to manage the Windows redimensioning, I add on my main window the event SizeChanged="MainWindow_SizeChanged" (In the case your RibbonGroupBox dimensions could also change, just add the same event on them).
private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
UpdateAlignRibbon();
}
private void UpdateAlignRibbon()
{
Groupe2.Width = MyWindow.ActualWidth - Groupe1.ActualWidth - Groupe3.ActualWidth;
}
In my case the Groupe3 RibbonGroupBox may change of dimension, so I call UpdateAlignRibbon() from 3 points :
After Initialization of my window(including defining the GroupBoxes content)
When the MainWindow has its dimensions changed
When Groupe1 or Groupe3 have its dimensions changed
Rover, You can try add RibbonGroup between last left RibbonGroup and Right align RibbonGroup and assign size to newly added ribbon related to window size.
example <RibbonGroup Width="400"></RibbonGroup>
it's looks following image
You can sort of hack in alignment but I'd recommend against it.
<r:RibbonGroup Header="This is a Filler Header With No Functionality but to Take Up Space" Visibility="Hidden">
<s:RibbonButton2/>
<s:RibbonButton2/>
<s:RibbonButton2/>
<s:RibbonButton2/>
</r:RibbonGroup>
The customer wanted their logo on the ribbon across the top of the page, but the more you add "false" elements to the bar, the quicker "true" elements will collapse when shrinking the window size.
Try this:
<RibbonTab Header="Home" x:Name="rtabHome" FlowDirection="RightToLeft" >
<RibbonGroup Header="Group">
<TextBlock Text="Example"/>
</RibbonGroup>
</RibbonTab>
Works with FlowDirection="RightToLeft".

displaying a canvas at two positions simultaneously

In C#, using WPF components, Is it possible to display a canvas (whose contents change at run time based on user input) at two positions on the screen? or in two windows? So basically, whatever happens in the canvas positioned at one place happens in the canvas positioned in the other place.
Do you need them both to be interactive?
If not, then you could use a VisualBrush to duplicate the Canvas to another location. The VisualBrush part won't be interactive, but it will mirror what happens on the other one.
So, there are 2 solutions :
create control containing your canvas & add them to required places and bind to your VM
use visualbrush as #Tim mentioned, example:
<Window x:Class="visualbrushmirroringstackoverflow.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">
<Window.Resources>
<VisualBrush x:Key="MirrorBrush"
Visual="{Binding ElementName=TargetCanvas}" TileMode="None"
Stretch="None" AutoLayoutContent="False"/>
</Window.Resources>
<StackPanel>
<Button Click="Button_Click" Content="Add Random Rects" Margin="5"/>
<Border BorderThickness="1" BorderBrush="Black" Margin="5">
<Canvas x:Name="TargetCanvas" Width="100" Height="100"
Background="White" />
</Border>
<Border BorderThickness="1" BorderBrush="Black" Margin="5">
<Rectangle Width="100" Height="100"
Fill="{StaticResource MirrorBrush}" />
</Border>
</StackPanel>
handler in code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
var rnd = new Random();
var element = new Rectangle { Fill = Brushes.Black, Width = 5, Height=5 };
Canvas.SetLeft(element, rnd.Next(100));
Canvas.SetTop(element, rnd.Next(100));
TargetCanvas.Children.Add(element);
}
If you're populating and updating the Canvas through databinding, you can create a usercontrol that defines the Canvas and all of it's styles, templates etc and bind each instance of that usercontrol to the same source object. Even in different windows, because they are updating from the same object in memory they should appear synchronised.
I had a same problem where i was asked to display a canvas in other window while retaining the original canvas.
What i did and you can do is this:
Since a single child cannot have multiple parents so you can make a copy of your original by serializing them using XamlReader.Save.
Put this canvas in a ViewBox (so that it stretches to its parent). Set contents of new window as this ViewBox.
Canvas copycanvas = XamlReader.Parse(XamlWriter.Save(OriginalCanvas)) as Canvas;
ViewBox vb = new ViewBox() { Stretch.Uniform, Child = copyCanvas };
Windows newwin = new Window() { Content = vb };
newwin.ShowDialog();

Categories