Resizing XAML properties - wpf

Is there a way to have XAML properties scale along with the size of the uielements they belong to?
In essence, I have a control template that I have created too large for it's use/ mainly because I want to use the same control with different sizes. The problem is that I can set the control size to Auto (in the ControlTemplate), however the properties of the intrisic template elements aren't resized: eg StrokeThickness remains at 10 while it should become 1.
It works fine when I apply a ScaleTransform on the template, but that results in a control that's too small when it's actually used: the width/height=Auto resizes the control to the proper size and then the scaletransform is applied. So I'm stuff with a sort of nonscalable control.
I'm a bit new to WPF, so there might be a straightforward way to do this...

Your description is a bit vague, but it sounds like you'll want to have a ViewBox with the Stretch set to Uniform as the root element of your control.

You could try to bind the width and height of the control inside the template to the width and height respectively of the templated control at runtime. Something like:
<Button>
<Button.Template>
<ControlTemplate TargetType={x:Type Button}>
<Border Width="{TemplateBinding Property=ActualWidth}"
Height="{TemplateBinding Property=ActualHeight}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Note that the binding sources are the FrameworkElement.ActualWidth and FrameworkElement.ActualHeight properties of the templated control. These properties contain the width and height of a control after it has been rendered, which can be different from what has been specified in the Width and Height properties. This is because the values are ultimately calculated at runtime taking into account the size of any parent controls in the visual tree.

Too bad you can't create a control template for a StackPanel, DockPanel, Grid, or any other container.

Related

WPF borders and the controls within them

This post is about the controls contained within a WPF Border control. It's also about having a border that can appear and disappear without affecting the contained controls.
For the record, I'm using C# and WPF and most of the view stuff is using XAML. I also use MVVM although I'm not sure that's going to be related.
What I had planned for was a border around a control that I could make appear and disappear, for the effect of a highlight or something like that. But when I change certain properties of the Border, for example the Opacity or Visiblity, they impact on the contained controls. I have also tried changing the Background property to Transparent and that has not made a difference.
I do know that some controls have a Border property, but that's not really the case for my situation.
How can I do this?
Thanks
Try this:
<Grid>
<Border BorderThickness="2">
<YourControl />
</Border>
<Border Opacity="0.5" BorderBrush="Red" BorderThickness="2" />
</Grid>
This way you can change the opacity of the second border without affecting your control. The trick is that Grid ensures that both elements inside it have the same dimensions.
Also notice how your control is wrapped in another border with the same thickness but with no brush. This is to keep the second border from obscuring your control.

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.

WPF Zoom but keeping some items width, height and position

What I want to get is any way for making zoom on a collection of controls but keep the width, height and position for a subset of this controls. I have seen the question How to keep element size while WPF zoom in and out?, seems very much to what I want, but is not answered and also is not very explicit so a will improve the question.
Currently I'm using the Zoombox control that comes with the WPF Toolkit extended for .net framework 4.0, but I can change it. The structure that I have is the following:
<Border x:Name="drawRegionBorder" Grid.Column="1" Grid.Row="1" d:LayoutOverrides="Width, Height" BorderThickness="1" CornerRadius="4" BorderBrush="{StaticResource BorderBrush}" >
<xctk:Zoombox x:Name="zoomBox">
<Grid x:Name="drawRegion" Height="{Binding Height}" Width="{Binding Width}" HorizontalAlignment="Left" VerticalAlignment="Top" Background="{DynamicResource DrawBackgroundBrush}">
<Image Source="{Binding Image}" ... />
<ListBox x:Name="points" ItemsSource="{Binding Points}">
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
<ListBox x:Name="paths" ItemsSource="{Binding SomePaths}">
<ListBox.Template>
<ControlTemplate>
<Canvas IsItemsHost="True"/>
</ControlTemplate>
</ListBox.Template>
</ListBox>
<!--... Others ...-->
</Grid>
</xctk:Zoombox>
</Border>
What I have here, are several list boxes, inside a grid, and the items panel for each list box is a Canvas, so each child (but the image) will be located inside a canvas and also each child will set the Canvas.X and Canvas.Y properties. So what I want, is any way of make the zoom (zoom-in or zoom-out), and keep the size of poitns (ellipsed) or paths...
Due the zoom, is a wpf's scale transformation, I suppose maybe a way for doing this when the zoom-in, make the zoom-out to the control I want to keep the size, and viceversa.
An example of the spected behavior is the blend designer, for instance when you zoom in a grid with rows and columns, the columns indicators keeps the original size, some thing like that is what I want for my points and paths.
I will appreciate any solution, maybe library, attached property, behavior or code.
Thaknks
The Blend designer uses Adorners for its manipulators. The sizes are calculated according to zoom the current zoom. If you're interested in using a similar technique, it's actually not that hard to do the calculation yourself.
You would create an adorner set to the Bounds of your control, then apply a scale factor according to the zoom in the designer. So if you zoom by 2.0, then you apply a RenderTransform of Scale 2.0 to your zoomed control, while calculating your adorner to be 2.0 of the ActualWidth and ActualHeight of the zoomed control (because those two properties do not take into account RenderTransformations). The nice thing is that since everything uses doubles, you get pixel perfect precision when doing this kind of calculation.
By using this approach, you gain the ability to zoom your main controls, while your manipulators simply scale to the zoomed controls, but maintain their control size throughout.
This tutorial is a good starting point. In the OnRender method is where you'd want to apply the scaling factor (by calculating the bounds of your adorner based on the UIElement's ActualWidth/Height, then multiplying by your scaling factor). How you apply your adorner depends a lot on application context - if you're doing a designer, then you'd want to apply the adorners in a design canvas or upon item selection.
I had made a research and found some useful things, for instace, if you are working with Adorners you can override the GetDesiredTransform in order to set what transform to do you want your adorner to do, here you can make null the transform made to your adorner. For more details see How to exclude scaleTransform from GeneralTransform in Adorner GetDesiredTransform method. In Msdn. But I think that if I want make it on controls I need to control the transform by my self.

Restrict the Width of a ContentPresenter's to its Dynamic Content

I would like to be able to dynamically adjust the size of a content control.
Here's a simple example:
...
<Slider x:Name="width" Minimum="40" Value="100" Maximum="300"/>
...
<ContentPresenter Width="{Binding Value, ElementName=width}" Content="Some value">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Grid MaxWidth="200" MinWidth="80">
<Rectangle Fill="Wheat" />
<TextBlock Text="{Binding}"/>
</Grid>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
Given this example, I would like to force the width of the content ContentPresenter to stay within the min and max width of its generated child (80 - 200 in this case).
Obviously with a simple example like this I could just change the range of the slider, but my real scenario is more complicated. I'm trying to restrict the size of a popup screen to its generated content. I can't set an explicit range on the popup because I have no idea what the content is going to be like before hand. The content has to be able to restrict itself.
Unfortunately MaxWidth and MinWidth on children are pretty much ignored. MinWidth results in cropping when the parent is set smaller. MaxWidth results in lots of empty space. It looks like I will have to set MaxWidth and MinWidth in the same place that I am dynamically updating the Width value.
My suggestion would be to replace the ContentPresenter with a custom panel (alternately, enclose a custom panel in the ContentPresenter if you cannot replace it) that doles out the exact size you want in the way that you want. It's fairly easy and simple to declare a class that derives from Panel and then override MeasureOverride and ArrangeOverride. In this case you could measure everything with the base MeasureOverride and then just assign the size you want as your available space. In ArrangeOverride just assign the direct child of your panel, to be the final size of the panel (rect = 0,0,width,height).

What exactly does Panel.IsItemsHost do?

What is the Panel.IstItemsHost attached property used for?
I see plenty of examples of people setting it on the ItemsContainer template for an ItemsControl, but the un-documentation over at MSDN does not explain why or what advantages setting property confers.
Say I have an ItemsControl. I want to use a custom panel that swoops items in and out as you scroll; its called a SwoopPanel. Now, how do I tell the ItemsControl to use my SwoopPanel to contain the templates it creates?
The quick way is to set the ItemsPanel on the ItemsControl:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<lol:SwoopPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
However, sometimes that doesn't work for you. Maybe you wish to customize how the SwoopPanel is presented in the UI, and the only way to get around this is to change the control template of the ItemsControl. Now you can add your SwoopPanel directly to the control template and, using the property, mark it as the ItemsHost that the ItemsControl will put all the templated items it creates.
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border CornerRadius="5">
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<lol:SwoopPanel IsItemsHost="True"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Do you have to do it one way or the other? No. Is one more advantageous than the other? Well, the second way allows you more control of the UI, the first way is easier. Take your pick, really. I've never personally done it the second way, but I think there might be a couple of places where it might be useful.
More Explanation, Please!
While all of the above answers are technically correct, I feel they don't illustrate how IsItemsPanel correlates to the ControlTemplate and the presence (or absence) of an ItemsPresenter and the corresponding ItemsPanel property which it uses. This answer will attempt to shed light on those things and hopefully clarify when you should, or shouldn't use each.
ItemsControls, Panels and IsItemsHost, Oh my!
An ItemsControl is simply a control that displays a collection of items. It does this by first generating individual containers* to represent the items visually, then it hands those containers over to a specific panel to be laid out for display on screen. As items are added or removed, the ItemsControl adds or removes the corresponding containers from the panel as needed.
* Note: If an item is already an instance of the container type (as determined by the result of the IsItemItsOwnContainer override of the ItemsControl)--i.e. you add a ListBoxItem instance to the Items collection of a ListBox--that item is simply passed through as-is directly to the panel, acting as its own container.
The specific panel used for hosting and laying out the containers is the first one found in the ItemControl's control template that has its IsItemsHost property set to 'True'.
There are two ways to specify which panel that is:
By inserting an ItemsPresenter into the ControlTemplate to act as a placeholder for the panel specified by the ItemsPanel property. (This is the most common way.)
By inserting a Panel directly into the ControlTemplate and explicitly setting its IsItemsHost property to True.
But which do you use and why? Read on to find out!
ItemsPresenter - "Have It Your Way!"
In a typical ControlTemplate for an ItemsControl such as a ListBox, the template specifies an ItemsPresenter somewhere inside of it. Here's a simplified excerpt showing how it's used:
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter />
</ScrollViewer>
</Border>
As you can see, there is an ItemsPresenter specified inside of a ScrollViewer in the middle of the template. What you don't see however is an actual panel to lay out the items.
So if there's no panel defined in the template, where does it come from? That's where the ItemsPanel property comes in. As its name suggests, this property defines which panel will be used to host and lay out the items. It doesn't however say where that panel appears in the ControlTemplate.
That brings us back to the ItemsPresenter. In short, it's a placeholder that essentially says "When the ItemsPanel property is set, I'll insert that panel here and set its IsItemsHost to True automatically."
The advantage of using an ItemsPresenter in the template for your ItemsControl is that you're making it very easy for consumers of your control to replace the panel without having to completely re-template your entire control.
IsItemsHost - "My Way or the Highway!"
However, what if you don't want someone to be able to change out your panel because your control depends on some custom panel implementation and anything else will break the functionality? In that case, you don't use an ItemsPresenter in your template. You instead need to specify the exact panel you want to use.
This is where IsItemsHost property comes into play. When set on a panel in the ControlTemplate, it tells that ItemsControl to use that specific panel to host the generated containers, regardless of what ItemsPanel is set to. The ItemsPanel property is essentially ignored.
Another benefit of specifying the panel directly in the template is you can then name it and access it just like any other template part.
Here's the same example as above, but rather than an ItemsPresenter, it hard-codes a SpecializedPanel to lay out the items. We indicate that's the panel we want to use to host the items by setting its IsItemsHost property to True and finally, we give it a name so we can access it directly from code.
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<SpecializedPanel name="PART_MainPanel" IsItemsHost="True" />
</ScrollViewer>
</Border>
In this case, because the template doesn't use an ItemsPresenter and instead directly includes a panel with its IsItemsHost set to True, there is no way for the user to change out that panel short of completely replacing the entire ControlTemplate. (As mentioned before, the ItemsPanel property is ignored.)
Bringing it all home...
To recap, if you're a control author and want to give consumers of your control the flexibility to swap out the panel used to lay out your items, then define your template for your ItemsControl using an ItemsPresenter. Make sure to also set the ItemsPanel property in the template to specify a default panel.
If however, want to 'lock' which panel your control uses, then do not use an ItemsPresenter in the ControlTemplate. Instead, specify the specific panel you want to use directly in the template, then set its IsItemsHost property to True.
Note: There's technically a third scenario, which is arguably more common: You're not a control author creating something to be consumed by other users, but rather are simply re-templating an ItemsControl (like say a ListBox) for some specialized use in your own application.
In that case, since you are the ultimate consumer of the control, you most likely won't have to worry about other consumers downstream needing to change out the panel, so it's completely fine to simply specify the panel directly in your template (again, setting its IsItemsHost true) and not worry about using an ItemsPresenter and its associated ItemsPanel property as the latter, while valid, would just add unnecessary complexity without any actual benefit.
Hope this clarifies exactly what's going on.
See http://msdn.microsoft.com/en-us/library/system.windows.controls.panel.isitemshost(v=vs.90).aspx
Essentially, what this post says is that if you are replacing the ControlTemplate of a ListBox and want a new layout, set IsItemsHost=true on some panel, e.g. a StackPanel. Then any items in the ListBox will be automatically added as children of the StackPanel. If the orientation of the ListBox is Horizontal, then the ListBox will be horizontal.
The other way is to set the ItemsPanel property of the ListBox to an ItemsTemplate and in that template you have a StackPanel. In this case the ListBox items will be added to the StackPanel children just as in the first case. However, you do not need to set IsItemsHost = true, it will have absolutely no effect. This is done for you by the fact that you are setting the ItemsPanel property.

Resources