Custom control size on InitializeComponent() - c#

I am trying to create a custom user control in WPF. I want to be able to set the size manually when I later use the control within another window.
As a short test I have just made a control comprising a canvas within a grid, which totally fills the control. When initialised it draws a rectangle within itself showing its size. I then position this on a window, making it whatever size I want.
However I now have problems, as if I make the height of the rectangle I draw
this.ActualHeight
then when the control initialises this value is still 0, and so I get nothing. If instead I use
this.Height
then I get the height that I made it during design time, and not the size I have subsequently made it within the window.
The height and width seem to be set within the XAML designer, so I don't know what to do.
Is there an easy way around this?

I think what you're experiencing is how WPF performs layout and specifically how Canvas does not participate in Layout.
In your specific case you are setting the width of the Rectangle to Canvas.ActualWidth? Unless the width / height of the canvas have been explictly set then the ActualWidth/Actualheight will be zero, hence you can't get a point of reference within which to position children of Canvas. What I would do is bind the canvas width and height to its parent container (or set in code) to get the ActualWidth / ActualHeight correctly propagated.
As a point of interest try this example to understand how the WPF Layout engine works. The following code can force a height on a FrameworkElement to set the Width, Height as normal, then force a layout (measure, arrange pass) on the element in question. This causes the WPF layout engine to measure/arrange the element and propagate the Width, Height to ActualWidth, ActualHeight.
For example:
// Set the width, height you want
element.Width = 123;
element.Height = 456;
// Force measure/arrange
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
element.Arrange(new Rect(0, 0, element.DesiredWidth, element.DesiredHeight));
// Subject to sufficient space, the actual width, height will have
// the values propagated from width/height after a measure and arrange call
double width = element.ActualWidth;
double height = element.ActualHeight;
Also see this related question for clarity. I have used the above method occasionally to measure the text in a textblock so as to position correctly on a canvas.

Related

Disappearing WPF controls after 'Arrange' with Infinity size

I'm facing an odd bug for around a month onwards.
I have a form that looks like this :
When I click on Save picture :
var g = new Grid();
var mv = new MyControl();
g.Children.Add(mv);
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
var rtb = new RenderTargetBitmap((int)g.ActualWidth, (int)g.ActualHeight,
96, 96, PixelFormats.Pbgra32);
rtb.Render(g);
Calling Measure() and Arrange() sets the size of the grid with proper size ( retrieved from MyControl )
The form control and the user control being rendered on the RenderTargetBitmap share no common data.
The visuals which become hidden are there and the viewmodel data is populated properly.
After executing this code and showing the dialog again it looks like this :
Both of the checkboxes have disappeared.Why?!
This problem doesn't appear if I set the size of the grid ( var g in the code )
var g = new Grid()
{
Width = 1024,
Height = 768
};
The default RowDefinition and ColumnDefinition for Grid is 1*. This means that the contents are given however much space the Grid has. In this case, the Grid will ask its parent to give it the maximum amount of space available for itself.
In your case, because your Grid has no visual parent, it has no idea how big it should render itself. You need to give it an explicit width/height. If you want to convert the contents of MyControl into Bitmap, why not just do it without the Grid?
If you insist on using Grid, and the size is dynamic, then you should manually specify the RowDefinitions and ColumnDefinitions. Change both of them to auto, which would force the Grid to size itself to the contents' sizes.
As said on MSDN info about Measure (Remarks section):
Computation of layout positioning in Windows Presentation Foundation
(WPF) is comprised of a Measure call and an Arrange call. During the
Measure call, an element determines its size requirements by using an
availableSize input. During the Arrange call, the element size is
finalized.
availableSize can be any number from zero to infinite. Elements
participating in layout should return the minimum Size they require
for a given availableSize.
When a layout is first instantiated, it always receives a Measure call
before Arrange. However, after the first layout pass, it may receive
an Arrange call without a Measure; this can happen when a property
that affects only Arrange is changed (such as alignment), or when the
parent receives an Arrange without a Measure. A Measure call will
automatically invalidate an Arrange call.
Layout updates happen asynchronously, such that the main thread is
not waiting for every possible layout change. Querying an element via
code-behind checking of property values may not immediately reflect
changes to properties that interact with the sizing or layout
characteristics (the Width property, for example).
Layout updates can be forced by using the UpdateLayout method.
However, calling this method is usually unnecessary and can cause poor
performance.
Hence call g.UpdateLayout in your save picture button click.
var g = new Grid();
var mv = new MyControl();
g.Children.Add(mv);
g.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
g.Arrange(new Rect(g.DesiredSize));
g.UpdateLayout();
var rtb = new RenderTargetBitmap((int)g.ActualWidth, (int)g.ActualHeight,
96, 96, PixelFormats.Pbgra32);
rtb.Render(g);

Why Image size is NaN (wpf) and how to resize via mouse wheel?

I want to load an image and put it into a viewbox. The image is displayed correctly, but, when I'm going to get it's width and height, it's both NaN.
This is my image load code :
Image img = new Image();
img.Source = (ImageSource)new ImageSourceConverter().ConvertFromString("1.png");
Viewbox vb = new Viewbox();
vb.Width = img.Width;
vb.Height = img.Height;
vb.Child = img;
cnv.Children.Add(vb);
The reason I want to get the image width and height is so I could resize it (via viewbox resize) later inside the application.
Any idea how to get the image dimension?
And this is how I'm going to resize it (via mouse wheel)
private void cnv_MouseWheel(object sender, MouseWheelEventArgs e)
{
if (vb != null)
{
vb.Width += Mouse.MouseWheelDeltaForOneLine;
vb.Height += Mouse.MouseWheelDeltaForOneLine;
}
}
And it returns error and said that vb.Width is not a valid value.
Questions to sum this up :
How to get the image width / height in wpf?
How to resize the viewbox (which will also result in image resize) via mouse wheel? For example, if I scroll up the wheel, the width and height is added by 1, and if I scroll down the wheel, the width and height is reduced by 1.
Thank you
P.S. Viewbox vb; and Image img; is a global variable, I just shorten the script down.
P.S.S. I know if the Viewbox width and height initialized by a number, lets say 100 and 100, it will work, I just wanna know how to get the image original size.
EDIT :
The resize can be achieved by detecting whether it's scrolled up or down by detecting e.Delta > 0 or e.Delta < 0
(Source : http://social.msdn.microsoft.com/Forums/vstudio/en-US/170c4fd0-1441-4d83-903d-594af75f8fb4/detect-mouse-scroll)
It seems as though the Image object is not fully loaded at that stage. I believe that you can use the Width and Height properties of the ImageSource class instead. That should be fully loaded at this stage.
ImageSource.Width Property
ImageSource.Height Property
For other users, you can also possibly find the values that you want from the ActualWidth and ActualHeight properties of the Image class (inherited from the FrameworkElement class) instead of the more usual Width and Height properties.
FrameworkElement.ActualHeight Property
FrameworkElement.ActualWidth Property
The image original size can only be obtained in Pixels while your WPF controls are measured in Device Independent Pixels so you're going to have to make a translation from Pixels to Device Independent Pixels and back wherever necessary.
That said, to obtain the width and height for a png file, you can load it into a BitmapImage instead of Image and query the PixelWidth and PixelHeight properties for the BitmapImage.
Again, be aware that this is the only place in your code where you're going to be dealing with Pixels, all Width and Height properties (including ActualWidth and ActualHeight, only set after the control is fully rendered) are measured in Device Independent Pixels. This can be misleading because in a lot of situations this happens to be a 1:1 conversion but this is not guaranteed to be the case.
More info can be found here: http://msdn.microsoft.com/en-us/library/windows/desktop/ff684173%28v=vs.85%29.aspx

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

Get WPF control height when it is set 'Auto'

I need get a WPF control height for calculate my next control margin top, but when I try get control height with textbox1.height, this is return 'Auto' and not numbers
What can I do for get control height, when it is set 'Auto'?
You should try this:
textBox1.ActualHeight
Remarks (by #Viv)
just make sure when you query for textBox1.ActualHeight, you do it once the control is Loaded. You're going to get 0.0 if you check ActualHeight before it's setup properly

How do I get the dimensions of a WPF element at run-time without specifying them at compile-time

The problem?
<UI:PanelBrowser Margin="12,27,12,32"></UI:PanelBrowser>
WPF is ridiculous in that not manually specifying properties (Such as Width and Height) in this case causes them to have the values Doulbe.NaN. The problem is that I need to know this number. I'm not going to manually set a width and height in the XAML because that stops it from resizing.
Given the above piece of XAML (this object is a simple subclass of the Border control), how can I get the values of the Width and Height properties at run-time?
Edit :
Wow, I feel ridiculous. I read about ActualWidth and ActualHeight, but they were consistently returning 0 and 0 for me. The reason is that I was testing for these properties in the constructor of the Framework Element, before they were actually initialized. Hope this helps someone who runs into the same issue and testing fallacies. :)
Try using the FrameworkElement.ActualWidth and ActualHeight properties, instead.
The WPF FrameworkElement class provides two DependencyProperties for that purpose: FrameworkElement.ActualWidth and FrameworkElement.ActualHeight will get the rendered width and height at run-time.
You can use elements' ActualWidth and ActualHeight properties to get the values of the width and height when they were drawn.
Use VisualTreeHelper.GetDescendantBounds(Visual Reference), and it return Rect.
Then Check the height of the Rect.
Ex)
Rect bounds = VisualTreeHelper.GetDescendantBounds(element);
double height = bounds.height;
OR
Use UIElement.Measure(Size size), it will assign the Size into DesiredSize.
Ex)
myElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double height = myElement.DesiredSize.Height;

Categories