I think I have understood how MeasureOverrride works, but I am trying to use it in a very simple case and It doesn't work... So now I'm not so sure... After using Measureoverride do I have to use Arrageoverride too or the system will do it for me? The situation is this one: I have a LinearLayout class inherited from Panel and it has two fields called wrapwidht and wrapheigh, if they are true the width or the height of the LinearLayout has to be as its children require. so my Measureoveride looks like:
protected override Size MeasureOverride(Size availableSize) {
Size panelDesiredSize = new Size();
if ((this.widthWrap) || (this.heightWrap))
{
foreach (UIElement elemento in this.Children)
{
System.Diagnostics.Debug.WriteLine(((FrameworkElement)elemento).DesiredSize.ToString());
if (this.Orientation.Equals(System.Windows.Controls.Orientation.Vertical))
{
if (this.widthWrap)
{
//the widest element will determine containers width
if (panelDesiredSize.Width < ((FrameworkElement)elemento).Width)
panelDesiredSize.Width = ((FrameworkElement)elemento).Width;
}
//the height of the Layout is determine by the sum of all the elment that it cointains
if (this.heightWrap)
panelDesiredSize.Height += ((FrameworkElement)elemento).Height;
}
else
{
if (this.heightWrap)
{
//The highest will determine the height of the Layout
if (panelDesiredSize.Height < ((FrameworkElement)elemento).Height)
panelDesiredSize.Height = ((FrameworkElement)elemento).Height;
}
//The width of the container is the sum of all the elements widths
if (this.widthWrap)
panelDesiredSize.Width += ((FrameworkElement)elemento).Width;
}
}
}
System.Diagnostics.Debug.WriteLine("desiredsizzeeeeeee" + panelDesiredSize);
return panelDesiredSize;
}
The children I am aading to the LinerLayout are 3 buttons, but nothing is drawn.. even if the panelDesiredSize filed is correct.. so maybe I didn't understand how it works very well. If anybody can help me would be very nice :-)
Check my answer on a previous post similar to yours: Two Pass Layout system in WPF and Silverlight
The answer is that no, you don't have to override ArrangeOverride, but what is the point of using MeasureOverride if you are not going to use ArrangeOverride?
You should call the Measure method on the child. See the example here: http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.measureoverride.aspx
You must call Measure on "elemento". It's during the Measure that Silverlight creates the UI elements declared in the elemento's template since they'll be needed to actually measure and come up with a desired size. You should then use the elemento.DesiredSize.Width and Height to come up with the desired size for your panelDesiredSize.
Related
I imagine it for example
public int ChildSize
{
get { return childSize; }
set
{
foreach (UIElement child in Children)
{
child.DesiredSize.Width = value;
}
}
}
I suspect you are writing a custom panel, which is quite an advanced task.
Your Panel should create overrides for the MeasureOverride and ArrangeOverride methods.
In these methods you should be calling each child's Measure and Arrange methods passing in appropriate Size and Rect according your determined childSize and calculated relative positions of each child.
I'm working on an application for a client, and one of the requirements is the ability to make appointments, and display the current week's appointments in a visual format, much like in Google Calendar's or Microsoft Office. I found a great (3 part) article on codeproject, in which he builds a "RangePanel", and composes one for each "period" (for example, the work day.) You can find part 1 here:
http://www.codeproject.com/KB/WPF/OutlookWpfCalendarPart1.aspx
The code presents, but seems to choose an arbitrary height value overall (440.04), and won't resize below that without clipping. What I mean to say, is that the window/container will resize, but it just cuts off the bottom of the control instead of recalculating the height of the range panels, and the controls in the range panels representing the appointment. It will resize and recalculate for greater values, but not less.
Code-wise, what's happening is that when you resize below that value, first the MeasureOverride is called with the correct "new height". However, by the time the ArrangeOverride method is called, it's passing the same 440.04 value as the height to arrange to.
I need to find a solution/workaround, but any information that you can provide that might direct me for things to look into would also be greatly appreciated (I understand how frustrating it is to debug code when you don't have the codebase in front of you. :) )
The code for the various Arrange and Measure functions are provided below. The CalendarView control has a CalendarViewContentPresenter, which handles several periods. Then, the periods have a CalendarPeriodContentPresenter, which handles each "block" of appointments. Finally, the RangePanel has its own implementation. (To be honest, I'm still a bit hazy on how the control works, so if my explanations are a bit hazy, the article I linked probably has a more cogent explanation. :) )
CalendarViewContentPresenter:
protected override Size ArrangeOverride(Size finalSize)
{
int columnCount = this.CalendarView.Periods.Count;
Size columnSize = new Size(finalSize.Width / columnCount, finalSize.Height);
double elementX = 0;
foreach (UIElement element in this.visualChildren)
{
element.Arrange(new Rect(new Point(elementX, 0), columnSize));
elementX = elementX + columnSize.Width;
}
return finalSize;
}
protected override Size MeasureOverride(Size constraint)
{
this.GenerateVisualChildren();
this.GenerateListViewItemVisuals();
// If it's coming back infinity, just return some value.
if (constraint.Width == Double.PositiveInfinity)
constraint.Width = 10;
if (constraint.Height == Double.PositiveInfinity)
constraint.Height = 10;
return constraint;
}
CalendarViewPeriodPersenter:
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement element in this.visualChildren)
{
element.Arrange(new Rect(new Point(0, 0), finalSize));
}
return finalSize;
}
protected override Size MeasureOverride(Size constraint)
{
this.GenerateVisualChildren();
return constraint;
}
RangePanel:
protected override Size ArrangeOverride(Size finalSize)
{
double containerRange = (this.Maximum - this.Minimum);
foreach (UIElement element in this.Children)
{
double begin = (double)element.GetValue(RangePanel.BeginProperty);
double end = (double)element.GetValue(RangePanel.EndProperty);
double elementRange = end - begin;
Size size = new Size();
size.Width = (Orientation == Orientation.Vertical) ? finalSize.Width : elementRange / containerRange * finalSize.Width;
size.Height = (Orientation == Orientation.Vertical) ? elementRange / containerRange * finalSize.Height : finalSize.Height;
Point location = new Point();
location.X = (Orientation == Orientation.Vertical) ? 0 : (begin - this.Minimum) / containerRange * finalSize.Width;
location.Y = (Orientation == Orientation.Vertical) ? (begin - this.Minimum) / containerRange * finalSize.Height : 0;
element.Arrange(new Rect(location, size));
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement element in this.Children)
{
element.Measure(availableSize);
}
// Constrain infinities
if (availableSize.Width == double.PositiveInfinity)
availableSize.Width = 10;
if (availableSize.Height == double.PositiveInfinity)
availableSize.Height = 10;
return availableSize;
}
So, after much hunting this morning, I found a workaround. If this gives insight and someone else finds a solution, I'll still mark yours as the accepted solution, because this just seems so hacky to me.
Basically, this morning, I pulled out the proverbial bludgeon, and decided to see what would happen if I set the MaxHeight to some value (100), and see how it would render then. It turns out, it scaled the control properly, and there was no clipping in sight! So, I figured I would try databinding the MaxHeight property to the window's height in the XAML. This didn't work, so I tried doing it from the code behind, and still had no luck. So, I went back to verify the phenomenon, and set the MaxHeight to 100 again, and ran the program. It resized to the size of the window, which was odd since hadn't I just set it to 100? Then I realized it was setting it to 100, and then the codebehind was overriding that value allowing it to scale upwards. Resizing the window even had the desired no-clipping effect... right up until you got to 100 height. So, I set the MaxHeight to zero, and that apparently goes inside the control and breaks some psuedo-minimum-height property, and then the window allows it to expand up to it's full size.
It's extremely hacky, but it works for now. =/ It seems so odd to me that I enabled the shrinking of a control by setting the MaxHeight property. ><
I'm trying to create a custom layout container, with the same characteristics of StackPanel, with the exception that it lays out the items starting at the right edge of the screen. Needless to say it does not work correctly.
I have identified a flaw inside ArrangeOverride() where the line
Point elementOrigin = new Point(this.DesiredSize.Width, 0);
simply creates a point # 0, 0. In other words this.DesiredSize.Width = 0. I understand that the measuring step happens before the arranging step, so I would expect this control will have the DesiredSize property set. How could I start rendering from the right side of the screen otherwise? Is it even possible?
Secondly the finalSize argument that is passed in to the function is much much larger than the area required by the three buttons I have defined in the test xaml. Something to the tune of 1676 by 909 vs a required 250 by 60 or so.
Thank you.
Here's my code:
protected override Size MeasureOverride(Size availableSize)
{
Size availableSpace = new Size(double.PositiveInfinity, double.PositiveInfinity);
Size desiredSize = new Size(0, 0);
foreach (UIElement child in this.Children)
{
child.Measure(availableSpace);
desiredSize.Width += child.DesiredSize.Width;
desiredSize.Height = Math.Max(desiredSize.Height, child.DesiredSize.Height);
}
return base.MeasureOverride(desiredSize);
}
protected override Size ArrangeOverride(Size finalSize)
{
Point elementOrigin = new Point(this.DesiredSize.Width, 0);
foreach (UIElement child in this.Children)
{
Rect childBounds = new Rect(elementOrigin, child.DesiredSize);
elementOrigin.X -= child.DesiredSize.Width;
child.Arrange(childBounds);
}
return base.ArrangeOverride(finalSize);
}
You need to simply return your desiredSize from your MeasureOverride implementation, you don't want to be calling the base version of this method, you are replacing the default implementation with yours.
Similarly with ArrangeOverride you are providing the implementation, you are replacing the default implementation so don't call the base version of this method. Simply return finalSize.
Using WPF, what is the most efficient way to measure a large number of short strings? Specifically, I'd like to determine the display height of each string, given uniform formatting (same font, size, weight, etc.) and the maximum width the string may occupy?
The most low-level technique (and therefore giving the most scope for creative optimisations) is to use GlyphRuns.
It's not very well documented but I wrote up a little example here:
http://smellegantcode.wordpress.com/2008/07/03/glyphrun-and-so-forth/
The example works out the length of the string as a necessary step before rendering it.
In WPF:
Remember to call Measure() on the TextBlock before reading the DesiredSize property.
If the TextBlock was created on-the-fly, and not yet shown, you have to call Measure() first, like so:
MyTextBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
return new Size(MyTextBlock.DesiredSize.Width, MyTextBlock.DesiredSize.Height);
In Silverlight:
No need to measure.
return new Size(TextBlock.ActualWidth, TextBlock.ActualHeight);
The complete code looks like this:
public Size MeasureString(string s) {
if (string.IsNullOrEmpty(s)) {
return new Size(0, 0);
}
var TextBlock = new TextBlock() {
Text = s
};
#if SILVERLIGHT
return new Size(TextBlock.ActualWidth, TextBlock.ActualHeight);
#else
TextBlock.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
return new Size(TextBlock.DesiredSize.Width, TextBlock.DesiredSize.Height);
#endif
}
It is very simple and done by FormattedText class!
Try it.
You can use the DesiredSize property on a rendered TextBox to get the height and width
using System.Windows.Threading;
...
Double TextWidth = 0;
Double TextHeight = 0;
...
MyTextBox.Text = "Words to measure size of";
this.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new DispatcherOperationCallback(delegate(Object state) {
var size = MyTextBox.DesiredSize;
this.TextWidth = size.Width;
this.TextHeight = size.Height;
return null;
}
) , null);
If you have a large number of strings it may be quicker to first pre-calualte the height and width of every indiviudal letter and symbol in a given font, and then do a calculation based on the string chars. This may not be 100% acurate due to kerning etc
I simply have two grid on top of one another. Given one state of the world, I want grid A to be on top, given another state of the world, I want grid B to be on top. In the old days we could just call grid.BringToFront(), but that doesn't exist anymore, and I can't figure out any way to make that happen.
The best I can figure, I need to create my own custom classes to allow this functionality, but that seems like major overkill for something that used to be so simple.
You can use the Panel.ZIndex property to change the display order of elements in a panel
You have to use the Z index property, and because there are no built-in function to do what you want, I made my own.
The higher the Z value, the 'closer' to front the control is.
So you want to put your control on top without having to set an arbitrary high Z value.
So here is a small function I wrote for myself to do exactly that.
Note: this assume that you are using a Canvas and UserControls.
So you might need to adapt it a little bit if that's not your case.
Basically it will get the index of the control to move, then any control currently above it will go down by 1 and the control to move will be put on top (to maintain hierarchy).
static public void BringToFront(Canvas pParent, UserControl pToMove)
{
try
{
int currentIndex = Canvas.GetZIndex(pToMove);
int zIndex = 0;
int maxZ = 0;
UserControl child;
for (int i = 0; i < pParent.Children.Count; i++)
{
if (pParent.Children[i] is UserControl &&
pParent.Children[i] != pToMove)
{
child = pParent.Children[i] as UserControl;
zIndex = Canvas.GetZIndex(child);
maxZ = Math.Max(maxZ, zIndex);
if (zIndex > currentIndex)
{
Canvas.SetZIndex(child, zIndex - 1);
}
}
}
Canvas.SetZIndex(pToMove, maxZ);
}
catch (Exception ex)
{
}
}
To whom it may concern:
ZIndex property is 0 by default, so if you have (like me) a Canvas with more than 1 element (>4000 Shapes in my case), all will have ZIndex = 0, so changing the ZIndexes with this method will have no effect.
For this to work, I set the ZIndexes to a known value after creating all the elements, so they can be ordered after.
int zIndex = 0;
for (int i = 0; i < canvas.Children.Count; i++) {
UIElement child = canvas.Children[i] as UIElement;
if (canvas.Children[i] is UIElement) Canvas.SetZIndex(child, zIndex++);
}
Instead of stacking the two grids, change the visibility properties so the grid you aren't using is collapsed.
Expanding on the answer from #PicMickael, this will do exactly as they described but with less instructions:
public void BringToFront<T>(T uiElement, Canvas canvas)
{
try
{
foreach (UIElement s in canvas.Children)
{
Canvas.SetZIndex(s, 1);
}
Canvas.SetZIndex(uiElement as UIElement, 2);
}
catch (Exception ex)
{
WriteLog.Error(ex);
}
}