I am writing MeasureOverride implementation and there is one point I am kind of stuck.
The return value of the function.
This is the code.
protected override Size MeasureOverride(Size availableSize)
{
Double cHeight = 0.0;
Double cWidth = 0.0;
Size size = new Size(0, 0);
foreach (UIElement child in InternalChildren)
{
child.Measure(new Size(availableSize.Width, availableSize.Height));
if (child.DesiredSize.Width > cWidth)
{
cWidth = child.DesiredSize.Width;
}
cHeight += child.DesiredSize.Height;
}
size.Width = double.IsPositiveInfinity(availableSize.Width) ? size.Width : cWidth;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? size.Height : cHeight;
return size;
}
My understanding is that returning an empty Size object is an indication that the element will use all the space available. However in this case when the space available is infinite positive, then it is returning zero.
Should it not be other way around. When infinite space is available then use only the space needed by the child elements? Otherwise constrain itself to use whatever space is available?
size.Width = double.IsPositiveInfinity(availableSize.Width) ? cWidth : availableSize.Width;
size.Height = double.IsPositiveInfinity(availableSize.Height) ? cHeight : availableSize.Height;
This one is kind of difficult to answer. It depends on what you want to achieve.
Probably you know that the measure phase determins the desired sizes only. The arrange phase fiddles with final values then.
I can imagine a panel with a behavior as in the code above. It could avoid a parent ScrollViewer to reserve too much space in case a child of our panel desires very much space (for example because it is an ItemsControl with many items itself). By returning zero the surrounding ScrollViewer would not reserve this space and in the arrange phase our panel could occupy space as needed although we returned zero.
It would be a very special case, but I was dicussing this very problem with a collegue today when he had a DataGrid along other elements within a ScrollViewer.
Without the code in "Arrange" this is all speculative but it's a possible usage of such code.
With MeasureOverride, you are returning the size that your control wants to be. If MeasureOverride is given infinite bounds then you can make it the size that holds all of the children or not; it is up to you. On the other hand, if you return a size that is larger than you are given, your control parent might accept that and just cut off your control later. For instance, if your control is in a grid and the grid width is set to 500, your MeasureOverride function will get passed an available width of 500. You can return 600 for the width and you will get that 600 width in the ArangeOverride function, but the parent grid will still only give your control 500 width and your content will just get cut off.
I tried this with my own PriorityStackPanel control written for WinRT (Windows 8.1 Store App) and it worked as I described. I think that WPF is just about identical in this case.
In WinRT, there is no "empty" size object. The size object will have some value set for it. NAN and PositiveInfinity both cause exceptions so you really should be returning the size that you want for your control. How you pick that size is up to you but it can't be a "you can decide for me: value like an empty size object (in WinRT). Even if you can return that in WPF, I advise against it since it makes the code less-than-clear. Just return the available size that you were given if that is what you need. Return a larger size if you need more room, but don't expect the container to show all of your content if you ask for more room than it is giving you.
Related
Title of my question could be make it look like a duplicate but please read ahead as my problem is a bit different.
I am trying to replicate the minimum size functionality of some popular media players like MPC-HC or VLC where when you try to make it small the minimum size it achieves is when only MenuStrip and Player Controls are visible.
The code I've written to attain this is:
public NewMain()
{
InitializeComponent();
int ClientTop = RectangleToScreen(ClientRectangle).Top;
int height = menuStrip1.Height + panel1.Height + ClientTop - Top;
label4.Text = height.ToString();
MinimumSize = new Size(373, height);
}
The problem is that when it runs, its not working perfectly and the menuStrip1 is still getting blocked a little at bottom from the panel1 (Docked at bottom) where the player controls will be placed.
Below is the Image of what I was able to attain with above code.
Next Image is what I expected:
Note that label on left updates when resize the form and the label on the right is the determined height via code.
My Idea was to add the difference of Form's Top and the Top of total rectangle visible on the screen i.e. the height of the title bar otherwise the resulting height will be even smaller and hide the menuStrip1 completely. I don't want to hardcode any values because it'll make the interface less adaptable to the changes that I might be doing later on.
To correctly determine the minimum height in this case is to keep the calculations relative which can be attained by:
int height = Height - (panel1.Top - menuStrip1.Bottom);
All credit goes to Hans Passant who provided this code. I'm just posting it as an answer to mark my question solved. Thank you.
The Windows Forms have Size and ClientSize Properties. Windows Forms also have a MinimumSize property that lets you set the smallest allowed size of the form. I am looking for a way to set the minimum ClientSize of a form. I'm new to c# and I am unsure of the best way to do this.
It occurred to me that I could use the Form.SizeChanged event to check and restrict the form size but this seemed messy and I am looking for another way of doing it.
Note: If the form border size is changed I want the Minimum Client Size to be maintained.
How to set a minimum Size of a Form's Client Area.
The Form.MinimumSize property sets the minimum size of the Form as a whole, including the current borders, the Caption and the subtle internal padding that is applied when the Form is resized and a new Layout is performed (many factors determine this activity).
It may be imperceptible, because it's fast (and, usually, nobody cares), but the relation between the Form Size and the Client Size is not constant. It can change when the Form is resized, dragging its borders. Or a System event causes this to happen.
Plus, changing the System settings, in many departments (the properties of the Windows, the Theme, Dpi, Font size etc.), can determine a change in the relations between the Form size and the size of its client area.
The good thing is that the Form is aware of these changes and, when notified of a System event that alters the aspect of Windows in some way, re-performs its layout.
When this happens (and it can happen frequently, always when a Form is resized), the OnLayout method is called.
Overriding OnLayout, allows to update values that rely on the Window/Client area measures.
➨ It can be interesting to see what happens when System settings,
affecting the aspect of the Windows, are changed while the application
is running. In this specific context, how many times the OnLayout
method is called and what the LayoutEventArgs properties are set
to.
This all considered, we can create a public MinimumClientSize Property.
We override OnLayout and reset the Form.MinimumSize to the new Form.MinimumClientSize plus the difference between the Form.Size and the Form.ClientSize.
For example, if we need to keep the Client Area size to a minimum of (500, 500):
private Size m_MinimumClientSize = new Size(500, 500);
public Size MinimumClientSize {
get => m_MinimumClientSize;
set { m_MinimumClientSize = value;
PerformLayout();
}
}
protected override void OnLayout(LayoutEventArgs e) {
base.OnLayout(e);
MinimumSize = m_MinimumClientSize + (Size - ClientSize);
}
If we add to the OnLayout method:
Console.WriteLine($"ClientSize: {ClientSize}");
Console.WriteLine($"MinimumSize: {MinimumSize}");
Console.WriteLine($"Size: {Size}");
it becomes clear that the relation between Form.Size and Form.ClientSize is not always the same.
We could also calculate the difference between Size and ClientSize this way:
var borderSize = new Size(
(SystemInformation.FrameBorderSize.Width * SystemInformation.BorderMultiplierFactor
+ (SystemInformation.Border3DSize.Width * 2)) * 2,
(SystemInformation.FrameBorderSize.Height * SystemInformation.BorderMultiplierFactor
+ (SystemInformation.Border3DSize.Height * 2)) * 2);
var captionSize = new Size(0, SystemInformation.CaptionHeight);
MinimumSize = MinimumClientSize + borderSize + captionSize;
These fixed measures are correct. In normal situations, they provides the same values.
Not always, though. Never, when a Form is resized to its MinimumSize.
Plus, we're only considering a Form with a 3d Border.
Well, we could also override WndProc...
If a parent control asks its children "How big do you want to be?", then what use is the availableSize parameter that's passed along? I've taken a peek via Reflector into the StackPanel's source and I still can't figure it out.
If the child wants to be 150x30, then it still reports 150x30 even if availableSize is 100x20, doesn't it? And if the child is expected to constrain itself to the availableSize, then that might as well be done on the size that's returned from calling MeasureOverride on the child - no point in passing that parameter.
Is there something that I'm not taking into account?
If the child wants to be 150x30, then it still reports 150x30 even if availableSize is 100x20, doesn't it?
It depends on the control, but generally the answer is no. In any case, the point is to give it the opportunity to fit itself to the container, but it is not required to do so.
Think about the difference between a Grid and a StackPanel. The Grid will typically size itself precisely to the available size. The StackPanel, by contrast, will size itself infinitely in one direction only (depending on its orientation), regardless of the available size. In the other direction, it will extend itself only as far as the space needed for its children, unless its "HorizontalAlignment" / "VerticalAlignment" is set to "Stretch", in which case it will stretch itself out to the available size in that direction.
The ViewBox is a more complex example that makes good use of "availableSize". It generally sizes itself to the available space, and scales/stretches its children depending on the values of "Stretch" and "StretchDirection".
The point is to give the element the opportunity to size itself correctly. After all the parent control might clip it if it doesn't respect the available size.
One has to differentiate between the following possibilities:
Container
Container has unlimited size (ScrollViewer) or wants to know how much space the child needs. In this case the availableSize in MeasureOverride() is infinite.
Container has limited size. In this case the availableSize in MeasureOverride() is the limited size.
Child
Child can adjust to Container size. In this case it returns availableSize if availableSize is not infinite. If it is infinite and Child cannot calculate size on its own, it can return 0.
Child has a fixed size. In this case it does not care about the availableSize but returns the fixed size.
The same thing happens again in ArrangeOverride(Size arrangeBounds), although with possibly different sizes. Therefore, do not use values calculated in MeasureOverride(), but recalculate them in ArrangeOverride().
arrangeBounds cannot be infinite. Instead of infinite, the container passes the available size calculated in its Arrange() method. The child can still use a different size. If it is too big, the container will clip it. If it is too small, the Container needs to align it somehow (ContentAlignment).
If a control (Container, Child) is fixed size or not depends also on properties like Width, MinWidth, MaxWidth, HorizontalAlignment, etc. Depending on these settings, a control demands in some parameter combinations a fixed size and in others it can adjust to the available size.
// Get the size of the canvas
Size size = new Size(surface.Width, surface.Height);
// Measure and arrange elements
surface.Measure(size);
surface.Arrange(new Rect(size));
For some reason this is returning an error reading:
UIElement.Measure(availableSize) cannot be called with NaN size.
Now what I'm doing is seeing if reading the StackPanel properties on the first page, and then when I load up on another page it will let me Edit it, but I get this error.
Try using surface.ActualHeight and surface.ActualWidth instead of surface.Height and surface.Width. The values are NaN currently.
The size contains NaN value. Thus it can't measure the size. Make sure that the passes surface.Wigth and surface. Height values are not NaN.
Try using ActualHeight and ActualWidth properties of the Canvas, which are containing the actual height and width values of the Canvas.
availableSize can be any number from zero to infinite. Elements participating in layout should return the minimum Size they require for a given availableSize
/ Get the size of the canvas
Size size = new Size(surface.ActualWidth, surface.ActualHeight);
// Measure and arrange elements
surface.Measure(availableSize);
surface.Arrange(new Rect(availableSize));
Before I go into my question, let me explain my setup:
First: I have a PictureBox that holds a Bitmap which is generated at runtime. This Bitmap can be different widths but always the same height.
Second: PictureBoxes do not support scrolling, therefore, I have the PictureBox docked in a panel. Initially, I had used the panel's autoscroll feature, but abandoned that after I discovered through this article that PictureBoxes have a size limit. I also learned that it's better to instead have small PictureBoxes and only draw what needs to be seen instead of the whole image.
Third: So I added a HScrollBar, which is fine and dandy, but I can't seem to figure out the math behind how big to make the scroller. I tried setting the maximum of the scrollbar to the length of the bitmap, but as you can see the size of the scroller is much smaller in mine than the one Windows puts in if I use the autoscroll feature.
My question is, what is the math behind the scroller size and how do I emulate that in my custom scrollbar?
Let me know if my question is unclear and I will try my best to make it more understandable. And thanks in advance for your help!
I figured out what was the problem. Perhaps I should have tried a little longer. :)
The answer lies in the LargeChange property. I let the Maximum at the total width of the bitmap and then set the LargeChange to the width of what I wanted to show. (i.e. the width of the PictureBox)
The size of the "scroller" is determined by the ratio of the value of LargeChange to the value of Maximum. For example, if the width to show (LargeChange) is 100 and the total width (Maximum) is 300 then the "scroller" size will be 1/3 of the scrollbar length. (100/300).
I got same problem too and tried to figure it out, I have a panel which contain another panel inside it called panelChild, and the default scrollbar is small, I need lager scrollbar, so I use HScrollBar to do that (display over-top of default scrollbar), I post my solution here, may be it helpful to someone
public Form() {
InitializeComponent();
hScrollBar.Maximum = panelChild.Width;
hScrollBar.LargeChange = panel.Width; // panel which contain panelChild, and this hScrollBar will same as panel scrollbar
hScrollBar.Scroll += HScrollBar_Scroll;
}
private void HScrollBar_Scroll(object sender, ScrollEventArgs e)
{
int diference = e.OldValue - e.NewValue;
foreach (Control c in panel.Controls)
{
c.Location = new Point(c.Location.X + diference, c.Location.Y);
}
}