Autosize inside a WrapPanel? - wpf

Need help with a xaml Layout.
I have a Wrapanel with two elements inside. The left one, MainGrid, needs to be fixed at 900 pixels. The second one, AuxillaryDataScrollViewer, I would like to be a minimum of 650 and a maximum of 100% if the WrapPanel wraps it. Is this possible?
Here's what I got so far:
<Window x:Class="scratch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="768" Width="1024">
<Grid>
<ScrollViewer>
<WrapPanel>
<Grid Width="900" Height="800" Background="Bisque"></Grid>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Hidden" Width="650">
<Grid Width="1500" Height="700" Background="Tomato"></Grid>
</ScrollViewer>
</WrapPanel>
</ScrollViewer>
</Grid>
</Window>
Thanks!
Edit adding more detail:
Client would like to do data entry and see the calculated results realtime in a panel to the right, but some of the computers in his lab are only capable of 1280pixels width so on those machines he would like the results to wrap to below the data entry form.

Since the requirement is so well defined, you could simply add a SizeChanged handler, and set the second element's width manually per the container's width. If the container is less than 900 + 650, then stretch the second element to 100% of the container.
public MainWindow()
{
SizeChanged += MainWindow_SizeChanged;
}
void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
{
double firstElementWidth = MainGrid.Width; // 900
double secondElementMinWidth = AuxillaryDataScrollViewer.MinWidth; // 650
double secondElementWidth;
// if wrapped, stretch to the container
if (e.NewSize.Width < firstElementWidth + secondElementMinWidth)
secondElementWidth = e.NewSize.Width;
// otherwise (not wrapped), fill the remainder of the first row
else
secondElementWidth = e.NewSize.Width - firstElementWidth;
}
Obviously this is quick-and-dirty. If you need something more robust, then I suggest writing a custom panel that adheres to the conventions you need. The WrapPanel can't stretch elements to the overall width, but there's no reason you couldn't design a panel that does.

Related

Creating a square UserControl

I am designing a user control that needs to be square, and to fill as much room as it is given (to give some context, it is a checkboard).
My user control looks like:
<Grid>
<!-- My 8 lines / colums, etc. , sized with "1*" to have equal lines -->
</Grid>
Now I would simply like to say "This grid has to be square no matter what room it has to expand".
Tried Solutions in vain:
I can't use a UniformGrid because I actually have the names of the lines & columns in addition, so I have a leading header row and column with different sizes.
If I use a Viewbox with Uniform it messes all up.
I tried using the classic
<Grid Height="{Binding RelativeSource={RelativeSource Self}, Path=Width}"> ... </Grid>
but it only works if I manually set the Width property. Otherwise, this constraint is ignored.
Conclusion
I'm out of idea, and I would really like to avoid setting Width / Height manually as this control may be used in many various places (ListItem templates, games, etc...).
Solution from suggestion:
A solution is available with some code-behind. I did not find a XAML only solution.
Grid is now:
<Grid SizeChanged="Board_FullControlSizeChanged">...</Grid>
And the event handler is:
private void Board_FullControlSizeChanged(object sender, SizeChangedEventArgs args)
{
double size = Math.min (args.NewSize.Height, args.NewSize.Width);
((Grid)sender).Width = size;
((Grid)sender).Height = size;
}
I initially tried modifying your binding to ActualWidth and it still did not work when the Grid was the top level element and in some cases it ended up expanding the control further than the available size. Hence tried some other ways of getting the required output.
Got 2 ways of maybe addressing this:
Since this is a view related issue (not breaking MVVM, keeping a square formation, if your ok with having a bit of code-behind, you could do something like)
private void OnSizeChanged(object sender, SizeChangedEventArgs e) {
double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
mainGrid.Width = minNewSizeOfParentUserControl;
mainGrid.Height = minNewSizeOfParentUserControl;
}
and in your xaml you would name your main top level grid "mainGrid" and attach the UserControl size changed event handler to the above function not the Grid itself.
However if you totally hate code-behind for whatever reason, you can be a bit more fancy and create a behavior such as
public class GridSquareSizeBehavior : Behavior<Grid> {
private UserControl _parent;
protected override void OnAttached() {
DependencyObject ucParent = AssociatedObject.Parent;
while (!(ucParent is UserControl)) {
ucParent = LogicalTreeHelper.GetParent(ucParent);
}
_parent = ucParent as UserControl;
_parent.SizeChanged += SizeChangedHandler;
base.OnAttached();
}
protected override void OnDetaching() {
_parent.SizeChanged -= SizeChangedHandler;
base.OnDetaching();
}
private void SizeChangedHandler(object sender, SizeChangedEventArgs e) {
double minNewSizeOfParentUserControl = Math.Min(e.NewSize.Height, e.NewSize.Width);
AssociatedObject.Width = minNewSizeOfParentUserControl;
AssociatedObject.Height = minNewSizeOfParentUserControl;
}
}
For the behavior your xaml would then look like:
<Grid>
<i:Interaction.Behaviors>
<local:GridSquareSizeBehavior />
</i:Interaction.Behaviors>
</Grid>
Did test these two methods with Snoop and the square size was maintained while expanding/shrinking. Do note both methods in the crux use the same logic(just a quick mock-up) and you might be able to squeeze some better performance if you update the logic to only update height when width is changed and vice versa instead of both and canceling a resize all together if not desired
Try putting your grid in a ViewBox: http://msdn.microsoft.com/en-us/library/system.windows.controls.viewbox.aspx
Here's a code sample I came up with:
The usercontrol:
<UserControl x:Class="StackOverflow.CheckBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Viewbox>
<Grid Background="Red" Height="200" Width="200">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Button Content="testing" Grid.Row="0"/>
<Button Content="testing" Grid.Row="1"/>
<Button Content="testing" Grid.Row="2"/>
</Grid>
</Viewbox>
</UserControl>
And the main window:
<Window x:Class="StackOverflow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StackOverflow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:AllNoneCheckboxConverter x:Key="converter"/>
</Window.Resources>
<Grid>
<StackPanel>
<local:CheckBoard MaxWidth="80"/>
</StackPanel>
</Grid>
</Window>
What this Viewbox will do is scale the control to the space it's given. Since the grid inside the viewbox is square, the grid will ALWAYS stay square. Try playing around with the MaxWidth property I used in the MainWindow.
You could bind the Height property to ActualWidth instead of Width:
<Grid Height="{Binding ActualWidth, RelativeSource={RelativeSource Self}}">
...
</Grid>
However, a better solution would be to use a Viewbox. The trick to avoid that it "messes all up" is to make its Child square by defining (sensible) equal values for Width and Height:
<Viewbox>
<Grid Width="500" Height="500">
...
</Grid>
</Viewbox>
Register wherever it suits you (usually in constructor or OnAttached()):
SizeChanged += Square;
and handle size with this:
private void Square(object sender, SizeChangedEventArgs e)
{
if (e.HeightChanged) Width = e.NewSize.Height;
else if (e.WidthChanged) Height = e.NewSize.Width;
}
I have solved this by setting the margin of the contained control from inside the parent control's size changed event.
In my case I have a 'sudoku grid' user control called SudokuBoard inside a standard Grid control called MainGrid (which fills the main window) and it only requires the following code;
private void MainGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
double verticalMargin = Math.Max((e.NewSize.Height - e.NewSize.Width)*0.5, 0.0);
double horizontalMargin = Math.Max((e.NewSize.Width - e.NewSize.Height)*0.5, 0.0);
SudokuBoard.Margin = new Thickness(horizontalMargin, verticalMargin, horizontalMargin, verticalMargin);
}

WPF Canvas Desired size

I have the following control:
<UserControl x:Class="FooBar.AnnotationControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="400" Width="500" >
<ScrollViewer Height="400" Width="500">
<Canvas Height="400" Width="500" Name="ctlCanvas" MouseLeftButtonDown="MouseLeftButtonDownHandler" MouseWheel="Canvas_MouseWheel" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="ZoomTransform" />
</Canvas.RenderTransform>
</Canvas>
</ScrollViewer>
</UserControl>
namespace FooBar
{
public partial class AnnotationControl : UserControl
{
public AnnotationControl()
{
InitializeComponent();
}
private void MouseLeftButtonDownHandler( object sender, MouseButtonEventArgs args)
{
//Do Something
}
private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
{
ctlCanvas.Measure(new Size(ctlCanvas.ActualWidth * ZoomTransform.ScaleX, ctlCanvas.ActualHeight * ZoomTransform.ScaleY));
}
}
}
I'm trying to get the scroll viewer to respond to the scaling of the Canvas. The call to Canvas.Measure doesn't appear to change the Desired size of the Canvas. Any idea what is going on here?
You should NOT call Measure on your own. This method is supposed to be called in the layout step, and not somewhere else. Also a RenderTransform doesn't change your Size. The RenderTransform is applied AFTER the actual Layout is done. So you have a scrollviewer that don't need to scroll its content, because its the same size. What you might want is LayoutTransform.
Canvas is the most primitive element and it simply not designed to work with the ScrollViewer. Use Grid/StackPanel/WarPanel/UniformGrid instead.
Ok, I seem to have found a solution. It looks like I can wrap my canvas with another canvas and when I scale it, I simply set the height and width for the outer canvas = initial height and width times the current X and Y scales of the ScaleTransform.

What is the best way to draw the grid for a Dots and Boxes game? (Silverlight)

I am trying to implement a Dots and Boxes style game in Silverlight for Windows Phone 7. What is the best way to draw the dot and box grid so that I get notified when someone touches the space between two boxes? What XAML elements should I look at using?
A polygon shaped like the image below, with an overlaid line, would be your best bet.
You will set the polygon fill (shown in blue) to 1% alpha so that it is not visible, but is hit-testable (0% alpha turns off hit testing).
If you create one as a usercontrol, you can simply place them around your grid of dots with 90% rotation on the vertical ones:
The dots can be simple ellipses (turn off isHitTestVisible on these):
You can then simply turn on/off the visibility of the lines in the user controls (which are always present for hit-testing):
I suggest a canvas for the outer control to give you fine position adjustment from code, but a grid will work too if you get the margin offsets right.
Usercontrol XAML (created with Blend):
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="DotsAndBoxes.Connector"
d:DesignWidth="280" d:DesignHeight="80">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Path Data="M27.706778,279.98367 L68.48111,239.30304 L266.99857,239.30304 L306.88052,278.89899 L266.99857,318.49493 L68.481102,318.49493 z"
Fill="#022E2EFB"
Stretch="Fill"
UseLayoutRounding="False"
IsHitTestVisible="True"
MouseLeftButtonDown="Path_MouseLeftButtonDown"/>
<Path Data="M0,40 L40.218182,40 L280,40" Height="5" Stretch="Fill" StrokeThickness="5" UseLayoutRounding="False" VerticalAlignment="Center" Stroke="White" IsHitTestVisible="False"/>
</Grid>
</UserControl>
Expose a "click" event on the User Control, that is called from a LeftMouseButtonDown event on the polygon and catch those click events in the high-level container:
namespace DotsAndBoxes
{
public partial class Connector : UserControl
{
public event EventHandler<System.Windows.Input.MouseButtonEventArgs> Clicked;
public Connector()
{
// Required to initialize variables
InitializeComponent();
}
private void Path_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (Clicked != null)
{
Clicked(this, e);
}
}
}
}
You could generate one of these polygons by hand as the coordinates required are quite simple.

Flowdocument isnt using the full width/height

I have a FlowDocument which I want to fill the entire width and height of my window. I have tried using the FlowDocumentPageViewer (no luck) and am now using a DocumentPageView. I still can't get it to dock/fill the entire space; it's just sitting in the middle, in the minimum size it can create (does it make sense?)
Here is my code:
public DocumentPageView GetPage()
{
FlowDocumentPageViewer viewer = new FlowDocumentPageViewer();
StreamReader reader = new StreamReader(location);
string data = reader.ReadToEnd();
reader.Close();
string xamlData = HtmlToXamlConverter.ConvertHtmlToXaml(data, true);
FlowDocument result = (FlowDocument)System.Windows.Markup.XamlReader.Load(new MemoryStream(System.Text.UnicodeEncoding.Default.GetBytes(xamlData)));
viewer.Document = result;
viewer.VerticalAlignment = VerticalAlignment.Center;
viewer.HorizontalAlignment = HorizontalAlignment.Center;
DocumentPageView pageView = new DocumentPageView();
pageView.VerticalAlignment = VerticalAlignment.Center;
pageView.HorizontalAlignment = HorizontalAlignment.Center;
pageView.Stretch = System.Windows.Media.Stretch.Uniform;
pageView.PageNumber = 0;
pageView.StretchDirection = StretchDirection.Both;
pageView.DocumentPaginator = ((IDocumentPaginatorSource)result).DocumentPaginator;
return pageView;
}
Please note that this code contains the combination of my two methods but only the DocumentPageView is currently used. This is the Xaml that is created from my HTML source:
<FlowDocument xml:space="preserve" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Paragraph TextAlignment="center" FontSize="22pt" FontFamily="arial">Test Paragraph</Paragraph>
<Paragraph TextAlignment="center" FontFamily="arial">Test second paragraph</Paragraph>
</FlowDocument>
If I resize the fonts the content is only resized vertically (please note that the stretch direction is set to both). Any ideas?
I had a similar problem with FlowDocumentScrollView, but this solution also seems to work with FlowDocumentPageView:
The FlowDocument is centered because it's PagePadding property is set to auto,auto,auto,auto. Setting PagePadding to 0 fixes this behavior.
<FlowDocumentScrollViewer VerticalScrollBarVisibility="Auto">
<FlowDocument PagePadding="0">
</FlowDocument>
</FlowDocumentScrollViewer>
The following lines are causing your elements to center themselves (which is not the same as stretch):
viewer.VerticalAlignment = VerticalAlignment.Center;
viewer.HorizontalAlignment = HorizontalAlignment.Center;
pageView.VerticalAlignment = VerticalAlignment.Center;
pageView.HorizontalAlignment = HorizontalAlignment.Center;
You can safely remove them, as the default alignment here is Stretch.
If you still want to center the viewer, explicitly define the page size (remember, there are 96 points in an inch, and the Margin and PageSize are set in points):
Width= 96 * 8.5
Height= 96 * 11
Could you specify where and how do you use the result of your GetPage() method? Is it xbap or desktop application?
I'm asking this, because the following document is displayed just perfectly right in Kaxaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<FlowDocumentPageViewer>
<FlowDocument >
<Paragraph TextAlignment="center" FontSize="22pt" FontFamily="arial">Test Paragraph</Paragraph>
<Paragraph TextAlignment="center" FontFamily="arial">Test second paragraph</Paragraph>
</FlowDocument>
</FlowDocumentPageViewer>
</Grid>
</Page>
PS: If it's a desktop application you can always find who causes problems by Snoop tool, form Pete Blois.
Update: Its a desktop application, the getpage() result is posted into a grid which docks/fills perfectly.
<Window x:Class="GreenWebPlayerWPF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="Auto" Width="Auto" WindowStyle="None" WindowState="Maximized" WindowStartupLocation="CenterScreen" Loaded="Window_Loaded" Closing="Window_Closing">
<Grid Width="Auto" Height="Auto" Name="TransitionContainer" Background="White" Margin="0">
//here the result from GetPage() method is inserted
</Grid>
</Window>
(this comment is written from another account)
#Anvaka: What i mean by perfectly right is that the document should "dock in container" that is the fonts should resize, it should fill the container in height and width. Now that im thinking about it, that may not seem like a proper behaviour for a flowdocument.
When i put the flow document in the container it is centered in middle of parent container (so far so good). but the parent container is not filling out its parent container so when i zoom or change font size, i would like the documentPageView container to grow in width and height, but remain centered.

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.

Resources