How to retrieve Canvas dimensions in the viewmodel - wpf

I've got a Canvas element in a resizable window; on this canvas are a number of Image and ArcElements that are connected together. I'm trying to get the position of the images to adjust relatively in response to a resize of the window/canvas, but for some reason I can't read the dimensions of the canvas.
The main window is defined as:
<Page>
<DockPanel LastChildFill="True">
<TextBox DockPanel.Dock="Top">Message</TextBox>
<Canvas></Canvas>
</DockPanel>
</Page>
I've hooked up MvvMLight's EventToCommand so that I can route the Canvas's LayoutUpdated or
SizeChanged events to my viewmodel; I tried databinding the Canvas's Width and Height properties, but the dimensions always came out as zero, which meant that all the images on the canvas would appear dead centre rather than positioned as desired.

It turns out I was heading in the right direction by using MvvmLight's EventToCommand; there is an attribute PassEventArgsToCommand that when set to True sends the event args to the appropriate RelayCommand in the viewmodel. So in the viewmodel, I initialised the command thusly:
Commands.ResizeCommand = new RelayCommand<SizeChangedEventArgs>(action => RecalculateObjectPositions(action));
and in the RecalculateObjectPositions method, I can access e.NewSize to find the new size of the canvas.

Related

Binding Canvas.Size to parent UserControl and then setting Canvas.Size in code

I have a wpf Canvas in a grid in a UserControl named 'root' and I bind the Width and Height of the canvas as follows:
Width="{Binding ElementName=root, Path=ActualWidth}"
Height="{Binding ElementName=root, Path=ActualHeight}"
On the canvas all kinds of DrawingVisual are drawn.
In code behind I set the Width end Height of my Canvas equal to ContentBounds.Right and ContentBounds.Bottom so that every DrawingVisual that I add to the canvas will be visible.
This seems to work all right but I am confused about the binding mentioned.
This is a one-way binding from the usercontrol's actual size to the size of the canvas.
Does the setting of Width and Height in code behind overrule this binding?
When I remove the binding the canvas is displayed equally as well, but the control is also used in other places and situations and might be needed then.
Does the setting of Width and Height in code behind overrule this binding?
Yes. Programmatically setting the value of a target property that has a one-way binding applied to it will clear the binding.

Autosize canvas in wpf

How can canvas in wpf be autosized? I have a canvas in scrollviewer and I will add several buttons and lines in this canvas in code behind. since I don't know the position of the buttons, I have to hard code a very large number for the width and height of the canvas or if I add too many buttons, it can only show part of them.
I try to set the Width and Height to Auto but it doesn't work.
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas Width="Auto" Height="Auto" Name="cv1"></Canvas>
</ScrollViewer>
</Grid>
The Canvas element is the only element that can not be automatically resized, because has no inherent layout characteristics. If you want the Control to be resized as child elements come in, you could use something deriving from Grid.
Try a UniformGrid instead of your Canvas ans fill it with the elements you want. It allows you to just add elements without any layout constraints that are handled by the UniformGrid. otherwise if you use a simple Grid, you will have to define a Position for your element by setting the Margin property of each child element.
Hope this helps.

"Modal Dialog" in WPF - make overlay block key events

I'm creating a WPF application containing a "Main-Content" - Layer containing a TabControl and a "Dialog" - Layer containing an ItemsControl.
The XAML looks like this:
<Grid>
<TabControl>
..Some Tabs
</TabControl>
<ItemsControl>
<ContentControl Content={Binding Dialog1Property} />
<ContentControl Content={Binding Dialog2Property} />
</ItemsControl>
</Grid>
Usually "Dialog1Property" and "Dialog2Property" are null which means the ItemsControl is invisible. Whenever I assign a Control to one of them, it is shown in front of the TabControl which is exactly what I want. If I assign a gray Rectangle with an opacity of 0.7 to one of the Dialog - Properties it creates a Gray overlay.
If I click on the Tab, which is slightly visible through the overlay, nothing happens - the Rectangle blocks Mouse Events. It is, however, still possible to focus the TabControl behind the overlay using the Tab-Key and therefore it is also possible to switch tabs even though a Dialog is shown.
Is there an easy way to tell the rectangle to somehow block key events as it allready does with Mouseclicks?
Regards
BBRain
Yes, on your Rectangle, subscribe to the event PreviewKeyDown.
<Rectangle Opacity="0.7" Fill="Green" PreviewKeyDown="Rectangle_PreviewKeyDown" />
In its handler, simply set e.Handled = true;
private void Rectangle_PreviewKeyDown(object sender, KeyEventArgs e)
{
e.Handled = true;
}
Since routed events prefixed with "Preview..." are tunneling, the elements under your rectangle won't recieve the input.

Resizing a Panels based on the size of user control

I'm trying to find a way to resize a LayoutPanel (DevExpress) based on the size of the user control that it contains. The user control is exposed using a ContentControl. Here's the relevant Code
This is the Layout panel and the coresponding view:
<dxd:LayoutPanel Caption="Search Criteria" CaptionImage="Images/Icons/DetailView.png">
<ContentControl Name="myContentControl" Content="{Binding Path=ProjectsSearchVM}"/>
</dxd:LayoutPanel>
The ProjectSearchVM is a property of the MainWindowViewModel, which is the DataContext for the code above. This property returns an object of type ProjectsSearchViewModel that is replaced by its corresponding View (containing a userControl) using a Resource File:
<DataTemplate DataType="{x:Type vm:ProjectSearchViewModel}">
<vw:ProjectSearchView />
</DataTemplate>
The problem is that my view is higher than the original size of the Layout Pannel. I'd like to bind the panel's MinSize to the size of my view (or the ContentControl containing it).
I tried this, but it doesn't work:
<dxd:LayoutPanel Caption="Search Criteria" CaptionImage="Images/Icons/DetailView.png">
<dxd:LayoutPanel.MinSize>
<Binding ElementName="myContentControl" Path="Size"/>
</dxd:LayoutPanel.MinSize>
<ContentControl Name="myContentControl" Content="{Binding Path=ProjectsSearchVM}" />
</dxd:LayoutPanel>
I'm still very new to WPF, so I'm sure the solution is simple.
Can anyone enlighten me?
The question was answered on the DevExpress site at this link:
http://www.devexpress.com/Support/Center/Question/Details/Q448884
For flyout, it involves overriding the control in the container, for example, if it's in a Border, something like this:
public class AutoSizeContainer : Border {
protected override void OnInitialized(EventArgs e) {
base.OnInitialized(e);
BaseLayoutItem item = DockLayoutManager.GetLayoutItem(this);
Child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
AutoHideGroup.SetAutoHideSize(item, new Size(Child.DesiredSize.Width + 6, Child.DesiredSize.Height + 6));
}
}
Making such an object the root object of the LayoutPanel in an AutoHide group, makes the flyout sizing correct.
I'm not familiar with DevExpress's LayoutPanel, but most standard WPF panels (Grid, StackPanel) "auto size" to contain their children. Are you setting hard Height and Width properties for the LayoutPanel (or does it only contain a Size property and not Height and Width like standard WPF controls)?
If you are forced to use hard Height/Width values, a common way to bind them would be like this:
<dxd:LayoutPanel ... Height="{Binding Height, ElementName=myContentControl}" Width="{Binding Width, ElementName=myContentControl}">
<ContentControl x:Name=myContentControl ... />
</dxd:LayoutPanel>
Normally, if you bind Heights and Widths you bind to ActualHeight or ActualWidth, but those properties do not exist on ContentControl. (Height and Width are only suggested values, so you may find that if the above binding is needed and works for you, you may need to tweak the values slightly with a value converter to account for margins or padding).

WPF: Making child elements' MinWidth/MinHeight constrain the Window

I have a WPF Window that contains a UserControl with a MinWidth and MinHeight. How can I prevent the user from resizing the Window down to a point where that UserControl's minimum size is violated?
Here's a simplified version of a Window I'm working on. My real app's UserControl is replaced here by a Border:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="OK"/>
<Button Content="Cancel"/>
</StackPanel>
<Border BorderBrush="Green" BorderThickness="10"
MinWidth="200" MinHeight="150"/>
</DockPanel>
</Window>
If I shrink the window small enough, the Border's right and bottom edges are cut off. I want to prevent the window from getting that small -- I want my window's minimum size to be exactly the point at which the Border is at its minimum size. Some frameworks (like the Delphi VCL) automatically aggregate child controls' minimum sizes up to the window; I expected WPF to do the same, but clearly it does not.
I could always explicitly set the Window's MinWidth and MinHeight, but to calculate those correctly, I would have to factor in the Buttons' ActualHeight, which would mean waiting for at least one layout pass (or calling Measure manually). Messy.
Is there any better way to keep the Window from resizing too small for its content?
The simplest way I've found is to tell the Window to size to its content:
<Window ... SizeToContent="WidthAndHeight" ...>
and then, once it's done sizing (which will take the child elements' MinWidth and MinHeight into account), run some code that sets MinWidth and MinHeight to the window's ActualWidth and ActualHeight. It's also a good idea to turn SizeToContent back off at this point, lest the window resize when its content changes.
The next question is, where to put this code? I finally settled on OnSourceInitialized:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
MinWidth = ActualWidth;
MinHeight = ActualHeight;
ClearValue(SizeToContentProperty);
}
I also tried the Loaded event, but in my case, that was too soon -- Loaded happens before databindings have been evaluated, and I had databindings that affected my layout (a Label with a binding for its Content -- its size changed after the binding took effect). Moving the code into OnSourceInitialized, which fires after databinding, corrected the problem.
(There were also other events that fired after binding, but before the window was shown -- SizeChanged and LayoutUpdated -- but they both fire multiple times as the window is shown, and again later if the user resizes the window; OnSourceInitialized only fires once, which I consider ideal.)
Have you tried using an ElementName style binding from the Window to the UserControl at hand? It seems like it would be feasible to bind the Window's MinWidth/Height to those of the Button. Still not pretty, but it shouldn't require extra passes once the binding is in place and will adapt if you change the values on the Button.

Resources