Window sizing constraints by content - wpf

I want the window to respect MinWidth/MinHeight and MaxWidth/MaxHeight specifications of the content control inside.
Some suggested using SizeToContent, but this only helps to set the initial window size, not the constraints.
Others suggested overriding MeasureOverride and setting window's Min/Max height and width there, but this seems to be somewhat unclean, considering that such a trivial problem should surely have a purely declarative solution.
Just to mention another solution which seems reasonable but does not work (and had been previously mentioned in an answer which got deleted): binding MinWidth of the window to MinWidth of the control does not take into account window decorations.

If the initial window size is set so that actual content size is not coerced by the content's MinWidth/MinHeight and MaxWidth/MaxHeight in the initial layout pass (for example, by using Window.SizeToContent="WidthAndHeight"), then following equations are true:
Window.ActualSize - Content.ActualSize =
Window.MinSize - Content.MinSize = Window.MaxSize - Content.MaxSize.
Based on these equations you can derive the following code:
public MainWindow()
{
InitializeComponent();
this.SizeChanged += OnWindowSizeChanged;
}
private static void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
{
var window = (Window)sender;
var content = (FrameworkElement)window.Content;
window.MinWidth = window.ActualWidth - content.ActualWidth + content.MinWidth;
window.MaxWidth = window.ActualWidth - content.ActualWidth + content.MaxWidth;
window.MinHeight = window.ActualHeight - content.ActualHeight + content.MinHeight;
window.MaxHeight = window.ActualHeight - content.ActualHeight + content.MaxHeight;
window.SizeChanged -= OnWindowSizeChanged;
}
I do not know how to achieve this efficiently using the pure declarative approach since the code should be ran just once after the initial layout pass.

Some suggested using SizeToContent, but this only helps to set the initial window size, not the constraints.
I worked around this by setting the MinWidth and MinHeight properties right after the windows was initialized:
MainWindow.xaml
<Window ... SizeToContent="WidthAndHeight">
...
</Window>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
SourceInitialized += (s, e) =>
{
MinWidth = ActualWidth;
MinHeight = ActualHeight;
};
}

Use Binding markup extension. A binding is wpf's way of saying when this property (source) changes update some other property (target). In this case the Grid's MinWidth property is the Source and your window's MinWidth property is the target.
<Window x:Class="MinMaxValuesOnWindows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="800"
MinWidth="{Binding ElementName=gridy, Path=MinWidth}"
MinHeight="{Binding ElementName=gridy, Path=MinHeight}"
MaxWidth="{Binding ElementName=gridy, Path=MaxWidth}"
MaxHeight="{Binding ElementName=gridy, Path=MaxHeight}">
<Grid Name="gridy" MinHeight="80" MinWidth="80" MaxHeight="300" MaxWidth="300"/>
</Window>
As you mentioned in the topic this does not completely work, but you can use a converter on the binding to add on the window frame's height and width before updating the binding target (might require a PInvoke). Since I doubt the window frame thickness is dynamically changing in your application this can probably just be constant value (not necessarily true if user changes themes).

Related

Stackpanel Visibility - what am I doing wrong . .

I want to make a ListBox containing StackPanels as its elements. The StackPanels will be created and added at runtime, in the C# code behind. .
The StackPanels will contain some images but at the moment none of the image stuff exists yet, so in this code I just wanted to make sure I could do the mechanics.
My XAML looks like this:
<Grid>
<ListBox Name="listBoxImages" BorderBrush="DarkGray" Width="600" Height="300" BorderThickness="3"
Margin="0" Padding="0" Background="#FFC0C0C0"/>
</Grid>
In the C# code-behind I deliberately set a background color of the Listbox different from the one in the XAML to verify I was accessing the ListBox properly in the code-behind.
listBoxImages.Background = Brushes.Blue; //just to show I'm accessing it . . .
That part works; the ListBox displays blue.
Then I went to add a StackPanel. Since there's nothing in it yet I gave it a height and width and a different background color, but I don't see anything. So I checked its visibility and it's false. So I tried setting the visibility using System.Windows.Visibility.Visible but it's still false after that.
StackPanel myStackPanel = new StackPanel();
myStackPanel.HorizontalAlignment = HorizontalAlignment.Left;
myStackPanel.VerticalAlignment = VerticalAlignment.Top;
myStackPanel.Background = Brushes.Bisque; // make something visible
myStackPanel.MinHeight = 50;
myStackPanel.Width = 50;
bool bResult = myStackPanel.IsVisible;
myStackPanel.Visibility = System.Windows.Visibility.Visible;
bResult = myStackPanel.IsVisible;
myStackPanel.Margin = new Thickness(10);
listBoxImages.Items.Add(myStackPanel);
Why is the StackPanel visibility false and is that the reason why I don't see it after adding it to the ListBox? (I'm sorry if this is a noob question)
IsVisible is set to true when it gets rendered on UI.
You can verify by hooking to Loaded event and see value of IsVisible in it by putting breakpoint on the handler.
myStackPanel.Loaded += (s, e) => bResult = myStackPanel.IsVisible;
Also, I verified with your posted code and can see StackPanel rendered on UI.
More verbose definition:
.........
listBoxImages.Items.Add(myStackPanel);
myStackPanel.Loaded += new RoutedEventHandler(myStackPanel_Loaded);
}
void myStackPanel_Loaded(object sender, RoutedEventArgs e)
{
bool isVisible = (sender as StackPanel).IsVisible;
}
Listbox is better populated with an items template. If you want to add arbitrary controls of different types, just use a stack panel.

How can I limit the height of a window to a percent of the screen resolution using the MVVM pattern?

I have a window that is configured in this way:
<Window x:Class="Catalogo.Views.dlgGenerosContenidosAsignarView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="dlgGenerosAsignar"
SizeToContent="WidthAndHeight"
WindowStartupLocation="CenterOwner">
I want the window to adjust to its content, so I use SizeToContent="WidthAndHeight". But if the content is too big, it can outsize the screen, so I would like to limit the value of the MaxHeight attribute to the height of the screen.
How could I do this using the MVVM pattern?
I could use binding to a property in my ViewModel that uses the System.Windows.SystemParameters to get the height of the screen and set a property that binds to the View, but I think that the size of the window should be set in the View, not the ViewModel, so I would like to know if there is another solution.
There is a lot of confusion around using the code behind, when following the MVVM methodology. This is really a perfect situation where you would want to use the code behind. As you correctly noticed, this is purely view related and should not be in the view model. It has no reason, or possible benefit from being in there.
So, if I were you, I'd add a handler to the Window.Loaded event, get your measurements and set the Window.Height to the desired Height:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assuming that you want the Height to be 80% of the screen Height
Height = SystemParameters.PrimaryScreenHeight * 0.8;
}

Silverlight/WPF: Retreiving the size of a UIElement once it has been rendered on screen

I have the following simple piece of code:
var canvas = new Canvas();
foreach (var ztring in strings)
{
var textblock = new TextBlock();
textblock.Text = ztring;
panel.Children.Add(textblock);
textblock.Measure(infiniteSize);
}
At this point I would expect any of the size properties (Height/Width, ActualHeight/ActualWidth, DesiredSize, RenderSize) to give me the size of the textblock. None of them do.
ActualHeight always gives 16.0 no matter what size font. ActualWidth changes according to the text length but not the font size.
I change the font size on the parent container and not the TextBlock itself.
I feel like I am missing some basic element of understanding the manipulation of silverlight elements from within the codebehind.
The question is: how do I get the real actual pixel size of my TextBlock?
Below is a sample that adds a TextBlock to a Canvas using code behind and once the TextBlock is rendered, it displays its height in the title of the window. Is that what you are looking for?
XAML:
<Window x:Class="HeightTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel TextBlock.FontSize="30">
<Canvas Name="_canvas" Height="200"/>
</StackPanel>
</Window>
Code behind:
using System.Windows;
using System.Windows.Controls;
namespace HeightTest
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBlock textBlock = new TextBlock();
textBlock.Text = "Hello";
Canvas.SetLeft(textBlock, 25);
textBlock.Loaded +=
(sender, e) =>
{
Title = textBlock.ActualHeight.ToString();
};
_canvas.Children.Add(textBlock);
}
}
}
Have you tried using a real container like a Grid instead of Canvas?
What if you try reading the ActualSize property after the Measure using a Dispatcher.BeginInvoke?

Wpf ScrollViewer Scroll Amount

Is it possible to change the amount that the WPF ScrollViewer scrolls? I am simply wondering if it's possible to change the scrollviewer so that when using the mouse wheel or the scrollviewer arrows, the amount of incremental scrolling can be changed.
The short answer is: there is no way to do this without writing some custom scrolling code, but don't let that scare you it's not all that hard.
The ScrollViewer either works by scrolling using physical units (i.e. pixels) or by engaging with an IScrollInfo implementation to use logical units. This is controlled by the setting the CanContentScroll property where a value of false means "scroll the content using physical units" and a value of true means "scroll the content logically".
So how does the ScrollViewer scroll the content logically? By communicating with an IScrollInfo implementation. So that's how you could take over exactly how much the content of your panel scrolls when someone performs a logical action. Take a look at the documentation for IScrollInfo to get a listing of all the logical units of measurment that can be requested to scroll, but since you mentioned the mouse wheel you'll be mostly interested in the MouseWheelUp/Down/Left/Right methods.
Here's a simple, complete and working WPF ScrollViewer class that has a data-bindable SpeedFactor property for adjusting the mouse wheel sensitivity. Setting SpeedFactor to 1.0 means identical behavior to the WPF ScrollViewer. The default value for the dependency property is 2.5, which allows for very speedy wheel scrolling.
Of course, you can also create additional useful features by binding to the SpeedFactor property itself, i.e., to easily allow the user to control the multiplier.
public class WheelSpeedScrollViewer : ScrollViewer
{
public static readonly DependencyProperty SpeedFactorProperty =
DependencyProperty.Register(nameof(SpeedFactor),
typeof(Double),
typeof(WheelSpeedScrollViewer),
new PropertyMetadata(2.5));
public Double SpeedFactor
{
get { return (Double)GetValue(SpeedFactorProperty); }
set { SetValue(SpeedFactorProperty, value); }
}
protected override void OnPreviewMouseWheel(MouseWheelEventArgs e)
{
if (ScrollInfo is ScrollContentPresenter scp &&
ComputedVerticalScrollBarVisibility == Visibility.Visible)
{
scp.SetVerticalOffset(VerticalOffset - e.Delta * SpeedFactor);
e.Handled = true;
}
}
};
Complete XAML demo of 'fast mouse wheel scrolling' of around 3200 data items:
note: 'mscorlib' reference is only for accessing the demonstration data.
<UserControl x:Class="RemoveDuplicateTextLines.FastScrollDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<local:WheelSpeedScrollViewer VerticalScrollBarVisibility="Auto">
<ListBox ItemsSource="{Binding Source={x:Type sys:Object},Path=Assembly.DefinedTypes}" />
</local:WheelSpeedScrollViewer>
</UserControl>
Fast mouse wheel:
You could implement a behavior on the scrollviewer. In my case CanContentScroll did not work. The solution below works for scrolling with the mouse wheel as well as draging the scrollbar.
public class StepSizeBehavior : Behavior<ScrollViewer>
{
public int StepSize { get; set; }
#region Attach & Detach
protected override void OnAttached()
{
CheckHeightModulesStepSize();
AssociatedObject.ScrollChanged += AssociatedObject_ScrollChanged;
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.ScrollChanged -= AssociatedObject_ScrollChanged;
base.OnDetaching();
}
#endregion
[Conditional("DEBUG")]
private void CheckHeightModulesStepSize()
{
var height = AssociatedObject.Height;
var remainder = height%StepSize;
if (remainder > 0)
{
throw new ArgumentException($"{nameof(StepSize)} should be set to a value by which the height van be divised without a remainder.");
}
}
private void AssociatedObject_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
const double stepSize = 62;
var scrollViewer = (ScrollViewer)sender;
var steps = Math.Round(scrollViewer.VerticalOffset / stepSize, 0);
var scrollPosition = steps * stepSize;
if (scrollPosition >= scrollViewer.ScrollableHeight)
{
scrollViewer.ScrollToBottom();
return;
}
scrollViewer.ScrollToVerticalOffset(scrollPosition);
}
}
You would use it like this:
<ScrollViewer MaxHeight="248"
VerticalScrollBarVisibility="Auto">
<i:Interaction.Behaviors>
<behaviors:StepSizeBehavior StepSize="62" />
</i:Interaction.Behaviors>
I wanted to add to Drew Marsh accepted answer - while the other suggested answers solve it, in some cases you cannot override the PreviewMouseWheel event and handle it without causing other side effects. Namely if you have child controls that should receive priority to be scrolled before the parent ScrollViewer - like nested ListBox or ComboBox popups.
In my scenario, my parent control was a ItemsControl with its ItemsPanel being a VirtualizingStackPanel. I wanted its logical scrolling to be 1 unit per item instead of the default 3. Instead of fiddling with attached behaviors and intercepting/handling the mouse wheel events, I simply implemented a custom VirtualizingStackPanel to do this.
public class VirtualizingScrollSingleItemAtATimeStackPanel : VirtualizingStackPanel
{
public override void MouseWheelDown()
{
PageDown();
}
public override void MouseWheelUp()
{
PageUp();
}
public override void PageDown()
{
LineDown();
}
public override void PageUp()
{
LineUp();
}
}
then we use that panel like we normally would in our xaml markup:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<controls:VirtualizingScrollSingleItemAtATimeStackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Obviously my scenario is contrived and the solution very simple, however this might provide a path for others to have better control over the scrolling behavior without the side effects I encountered.
I did this to ensure whole numbers on scrollbar1.ValueChanged:
scrollbar1.Value = Math.Round(scrollbar1.Value, 0, MidpointRounding.AwayFromZero)

WPF UserControl Design Time Size

When creating a UserControl in WPF, I find it convenient to give it some arbitrary Height and Width values so that I can view my changes in the Visual Studio designer. When I run the control, however, I want the Height and Width to be undefined, so that the control will expand to fill whatever container I place it in. How can I acheive this same functionality without having to remove the Height and Width values before building my control? (Or without using DockPanel in the parent container.)
The following code demonstrates the problem:
<Window x:Class="ExampleApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:ExampleApplication3"
Title="Example" Height="600" Width="600">
<Grid Background="LightGray">
<loc:UserControl1 />
</Grid>
</Window>
The following definition of UserControl1 displays reasonably at design time but displays as a fixed size at run time:
<UserControl x:Class="ExampleApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid Background="LightCyan" />
</UserControl>
The following definition of UserControl1 displays as a dot at design time but expands to fill the parent Window1 at run time:
<UserControl x:Class="ExampleApplication3.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightCyan" />
</UserControl>
For Blend, a little known trick is to add these attributes to your usercontrol or window:
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="600"
This will set the design height and width to 500 and 600 respectively. However this will only work for the blend designer. Not the Visual Studio Designer.
As far as the Visual Studio Designer your technique is all that works. Which is why I don't use the Visual Studio Designer. ;)
In Visual Studio add the Width and Height attribute to your UserControl XAML, but in the code-behind insert this
public UserControl1()
{
InitializeComponent();
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
this.Width = double.NaN; ;
this.Height = double.NaN; ;
}
}
This checks to see if the control is running in Design-mode. If not (i.e. runtime) it will set the Width and Height to NaN (Not a number) which is the value you set it to if you remove the Width and Height attributes in XAML.
So at design-time you will have the preset width and height (including if you put the user control in a form) and at runtime it will dock depending on its parent container.
Hope that helps.
Here are a list of Design-Time Attributes in the Silverlight Designer. They are the same for the WPF designer.
It lists all of the d: values available in the Designer such as d:DesignHeight, d:DesignWidth, d:IsDesignTimeCreatable, d:CreateList and several others.
I do this all the time. Simply set the width and height values to "auto" where you instantiate your control, and this will override the design-time values for that UserControl.
ie: <loc:UserControl1 Width="auto" Height="auto" />
Another option is to set a combination of MinWidth and MinHeight to a size that allows design-time work, while Width and Height remain "auto". Obviously, this only works if you don't need the UserControl to size smaller than the min values at runtime.
I was looking for similar solution like the one used in Blend and with your mentions I created simple behavior class with two attached properties Width & Height that are applied only in DesinTime
public static class DesignBehavior
{
private static readonly Type OwnerType = typeof (DesignBehavior);
#region Width
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached(
"Width",
typeof (double),
OwnerType,
new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(WidthChangedCallback)));
public static double GetWidth(DependencyObject depObj)
{
return (double)depObj.GetValue(WidthProperty);
}
public static void SetWidth(DependencyObject depObj, double value)
{
depObj.SetValue(WidthProperty, value);
}
private static void WidthChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(depObj)) {
depObj.SetValue(FrameworkElement.WidthProperty, e.NewValue);
}
}
#endregion
#region Height
public static readonly DependencyProperty HeightProperty =
DependencyProperty.RegisterAttached(
"Height",
typeof (double),
OwnerType,
new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(HeightChangedCallback)));
public static double GetHeight(DependencyObject depObj)
{
return (double)depObj.GetValue(HeightProperty);
}
public static void SetHeight(DependencyObject depObj, double value)
{
depObj.SetValue(HeightProperty, value);
}
private static void HeightChangedCallback(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
if (DesignerProperties.GetIsInDesignMode(depObj)) {
depObj.SetValue(FrameworkElement.HeightProperty, e.NewValue);
}
}
#endregion
}
Then in your UserControl you just set these properties in Xaml
<UserControl x:Class="ExtendedDataGrid.Views.PersonOverviewView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tool="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:b="clr-namespace:ExtendedDataGrid.Behaviors"
b:DesignBehavior.Width="600" b:DesignBehavior.Height="200">
<Grid>
...
</Grid>
</UserControl>
Use MinWidth and MinHeight on the control. That way, you'll see it in the designer, and at runtime it will size the way you want.
I do it similar, but my solution assures that if you add your control to an container in design mode, it will appear reasonably.
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (this.Parent != null)
{
this.Width = double.NaN;
this.Height = double.NaN;
}
}
what do you think?
Thanks to the original answerer for this solution! For those that are interested, here it is in VB:
If LicenseManager.UsageMode <> LicenseUsageMode.Designtime Then
Me.Width = Double.NaN
Me.Height = Double.NaN
End If
Some have suggested using the LicenseManager.UsageMode property which I've never seen before but I have used the following code.
if(!DesignerProperties.GetIsInDesignMode(this))
{
this.Width = double.NaN;
this.Height = double.NaN;
}
esskar,
I just want to add that you should generally always call the method of the base when overriding an "On" method.
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
...
}
Great workaround by the way, I'm using it myself now too.

Resources