Synchronizing WPF control widths in a WrapPanel - wpf

I have this case
<WrapPanel>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox>Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
I want all the CheckBoxes inside the WrapPanel to be the same width.
Adding the following almost accomplishes the desired effect
<WrapPanel.Resources>
<Style TargetType="CheckBox" BasedOn="{StaticResource {x:Type CheckBox}}">
<Setter Property="MinWidth" Value="75" />
</Style>
</WrapPanel.Resources>
However, I do not want to hardcode a specific width, rather let the largest CheckBox set the width (the above also fails if any width > 75).
The Slider is independent and should be allowed to be larger than the CheckBoxes.
I do not want to use a Grid (with IsSharedSizeScope) since I do not want a hardcoded number of columns.
This article presents an interesting solution, but it would be nice to solve the problem without creating a custom control or using C# code.
What is the best way to do this, preferrably in XAML only?

I originally looked at this using IsSharedSizeGroup but hit a roadblock with making it dynamically apply to things instead of explicitly wrapping items. In this case, creating an AttachedProperty in code or another code based solution may in the long run be better then a XAML only approach. However, to create a purely XAML solution we can make use of the SharedSizeGroup property on a ColumnDefinition to share the sizes of each element and then use set the IsSharedSizeScope property on the WrapPanel. Doing so will make all of the contents in the WrapPanel with the same SharedSizeGroup share their width for columns and height for rows. To wrap the ComboBoxes and possibly ComboBoxes that are not currently in the XAML but will be added to the WrapPanel, we can create a Style and re-template the ComboBox to bassicly wrap it with a Grid.
<WrapPanel Grid.IsSharedSizeScope="True">
<WrapPanel.Resources>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<Grid Background="LightBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="WrapPannelGroup" />
</Grid.ColumnDefinitions>
<CheckBox Style="{x:Null}"
IsChecked="{TemplateBinding IsChecked}">
<!--Other TemplateBindings-->
<CheckBox.Content>
<ContentPresenter />
</CheckBox.Content>
</CheckBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</WrapPanel.Resources>
<CheckBox>Really long name</CheckBox>
<CheckBox>Short</CheckBox>
<CheckBox IsChecked="True">Longer again</CheckBox>
<CheckBox>Foo</CheckBox>
<Slider MinWidth="200" />
</WrapPanel>
Here we are re-templating all CheckBoxes without a style inside the WrapPannel to instead be CheckBoxes surrounded by a Grid. However, because of this we need to re-bind all of the CheckBoxes properties that we want to maintain. While that could become burdensome, it also allows for a pure XAML approach.

You can add a property or a converter that does the needed work, then bind each column's width to it. The property or converter can access the entire list of items, finding the widest one, and returning the desired width for all elements.

The best way to do this is to use a CustomControl like the article you posted.
Any solution you come across is going to have to iterate through the list of items and find the maximum width during the measure phase.
Any sort of XAML-only answer would have to be provided OOTB (e.g. IsSharedSizeScope), or would leverage some sort of multi-binding to link the items together. Thus any sort of XAML answer would be full of markup which makes it more verbose (and less elegant).
The only modification that I see to the CodeProject article you posted is adding the ability to "turn-off" consideration of certain elements (like your slider). This could be done as an additional attached property.

Related

Clarification about WPF Styles

In the below code, why might the first example fail to set the background to Blue, but, the second example work as one might expect they both would – that is, is sets the background to Blue? Interestingly, when the style is applied in the second example, even though the BorderThickness is not specified in the Style, the property value of "3" also gets picked up, presumably because the new Style does not set it at all.
Code 1:
<GroupBox Margin="4,12,4,4"
Grid.ColumnSpan="4"
Grid.Column="0"
Grid.Row="3"
Header="{x:Static res:UIResources.DepreciationText}"
BorderBrush="{DynamicResource MainControlBorderBrush}"
BorderThickness="3"
Background="Blue" />
Code 2:
<GroupBox Margin="4,12,4,4"
Grid.ColumnSpan="4"
Grid.Column="0"
Grid.Row="3"
Header="{x:Static res:UIResources.DepreciationText}"
BorderBrush="{DynamicResource MainControlBorderBrush}"
BorderThickness="3">
<GroupBox.Style>
<Style TargetType="GroupBox">
<Setter Property="Background"
Value="Blue" />
</Style>
</GroupBox.Style>
</GroupBox>
You could reason from the above observation that the following are true:
1) Some Style is getting applied to the GroupBox further up the tree – perhaps even to some base class of GroupBox, such as Control, since a search for a Style targeting GroupBox was not found.
2) A property set on a control instance will not override the same property set in a Style targeting the control.
3) There is not a way to augment an inherited Style, other than using the BasedOn property. Using the BaseOn property implies you must know the Key of the Style you would like to base it on, unless, if you want to use BasedOn with a Style applied to a Type, you could somehow specify that – perhaps using the Type name in BasedOn?
Can anyone confirm or correct the above assertions, and whether they correctly explain the observed result?
Are you using any sort of theme pack for your application that restyles controls? My guess would be that you have a style somewhere overriding the control template in such a way that the Background property is completely ignored. If the control template doesn't contain a {TemplateBinding Background}, the Background property does nothing.
You can definitely override properties by setting them explicitly, even if they are also set in a style.
By setting the style yourself without using a BasedOn, it implicitly uses the default control template for the GroupBox, rather than resolving to a style imported with your resources. If you wanted to use the imported resource style, you could do this:
<Style TargetType="GroupBox" BasedOn="{StaticResource {x:Type GroupBox}}">
<Setter Property="Background" Value="Blue" />
</Style>
I suspect this would give you the same result as in the first case, as now you would be inheriting the offending control template that ignores your Background value.

WPF ComboBox: How to you utilise a generic ItemContainerStyle with binding

I want to utilise a generic style for my ComboBoxItem content and have the text content bound to different properties on my underlying class. So this is the best I can come up with but the bindings are hard coded. So for every class bound to a combobox using this ItemContainerStyle I'd have to implement a "MainText" and "SubText" property.
Question is, is there a way to have the binding soft coded so where the style referenced from a combobox I can specify which string properties of the underlying class are used.
<Style TargetType="{x:Type ComboBoxItem}" x:Key="ComboBoxItemStyleA1">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="BB" Padding="8,3,8,3" Background="DarkGreen">
<StackPanel Margin="0">
<TextBlock Foreground="White" FontSize="16" Text="{Binding MainText}"/>
<TextBlock Foreground="White" FontSize="8" Text="{Binding SubText}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" TargetName="BB" Value="#FF256294"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And to use the style...
<ComboBox ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource ComboBoxItemStyleA1}" />
Further to dowhilefor's answer (many many thanks - WPF is great but sure is a voyage of discovery)
I used a data template to define the cell look originally - and then wanted to use a comboboxitem based style with a control template defined where I could specify the onmouseover triggers. i.e. these were to change the background color etc.
Butj
a) I couldn't remove the Border section of the template above - the triggers are tied to it by targettype="BB". so I kind of wanted to get the trigger bound to the container such that the datatemplate would pick up the background from the template binding but not sure how to get this plumbed in.
b) I realised that even if I comment out the BB specific bindings on the triggers just to get it to run - the combobox doesn't find and use the DataTemplate I defined. Seems that defining the controltemplate in my comboboxitemstyle stops it picking up the datatemplate.
I hope I make sense here - bottom line is I just want a style that I can apply with triggers in that set the background color of my cobobox item. It should not know what the data is - i.e. be able to plug in a datatemplate that will (template ?) bind to this background color.
Many thanks for the very fast response.
btw I'm using ItemContainerStyle in conjuction with ItemTemplate so I can have a different representation in the dropdown to what appears in the combobox list
First of all don't use the ItemContainerStyle for that. To be more precise never have any Bindings to the datacontext inside an ItemContainerStyle, at least try not. Why? The Style is used for defining the appearance of a combobox item disregarding the content. If you want to define how the content should look like, you use a DataTemplate for that. There are multiple ways to tell the combobox where he can find a proper DataTemplate for the Data you supply. Checkout the property ItemTemplate, ItemTemplateSelector and search for implicit styles, to find out more about them.
So to your problem, create one ItemContainerStyle for you combobox (if you really have to anymore) which doesn't care about the object that will be put into. Now you still need to provide multiple DataTemplates each and everyone with the knowledge of the data object that you want to be templated. There is no way around it, there is no soft databinding. Just try to keep your templates small and simple. If for some reason you need the exact same template, but your properties are just named differently, why not use a wrapper item for the DataContext with the properties Caption, Description and you can decide in code how these properties are filled with your real data wrapped into this object.

x:Shared MarkupExtension in Silverlight

Is there a workaround for the missing x:Shared MarkupExtension in silverlight?
I have the following Xaml which is creating an ellipse on each target series. I need the ellipses to be unique as they are later added to canvas. By using this Xaml I get the error that the UIElement has already been added to another parent (e.g. single Ellipse instance added to Canvas multiple times).
In WPF I simply use the x:Shared property on this style to get it to work.
<!-- Set the style for the series -->
<Style TargetType="SciChart:FastLineRenderableSeries" >
<Setter Property="SeriesColor" Value="#FF93F2C1"/>
<Setter Property="ResamplingMode" Value="Mid"/>
<Setter Property="RolloverMarker">
<Setter.Value>
<Ellipse Width="9" Height="9" Fill="#7793F2C1" Stroke="#FFA3FFC9"/>
</Setter.Value>
</Setter>
</Style>
A workaround I considered was to create a control called RolloverMarker and set its control template. I'd appreciate any direct or indirect solutions to this problem.
If you are dynamically adding objects to a panel, then a new object needs to be created each time, or you need to define your control in some kind of Template and add a new data object which will use the Template. You cannot add the same item multiple times.
For example,
// Does not work
var templateItem = new FastLineRenderableSeries();
myCanvas.Add(templateItem);
myCanvas.Add(templateItem);
// Works
myCanvas.Add(new FastLineRenderableSeries());
myCanvas.Add(new FastLineRenderableSeries());
Or
<ItemsControl ItemsSource="{Binding SomeCollection}"
ItemTemplate="{StaticResource FastLineRenderableSeriesStyle}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
// Add items. They'll get rendered with defined ItemStyle.
var templateItem = new FastLineRenderableSeries();
SomeCollection.Add(templateItem);
SomeCollection.Add(templateItem);

<TextBlock Text="Random" width="*"/> gives an error

I know for certain objects, the width can be specified as width="*", so that the width is as large as possible. However when I tried it with TextBlock, it gave an error.
Is there any way of specifying the width of a textblock to be as large as possible?
"*" is only valid for grid row/column sizing, AFAIK. You want HorizontalAlignment="Stretch".
UPDATED: Since your comment indicates you're doing this in a ListBox, you also need to set the ListBoxItem's HorizontalContentAlignment to "Stretch". Put this in your element:
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The "*" syntax is only supported by RowDefinition and ColumnDefinition in the Grid. You can use "Auto" in XAML to have it automatically determine the best Width/Height. Or in code-behind you can assign the Width/Height to double.NaN.
Have you tried placing the TextBox within a panel type that autoexpands (i.e. Grid). There is a fairly decent example here.

Accessing underlying ViewModel properties within a WPF DataGrid GroupItem DataTemplate

I have a grouped WPF DataGrid (the standard Microsoft one) representing some data on the UI for our users.
In order to show totals within the grouped regions, we are overriding the GroupItem DataTemplate as follows in XAML:
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border BorderBrush="DarkGray" BorderThickness="1" Padding="12,0">
<Expander VerticalContentAlignment="Center" IsExpanded="{Binding ., Converter={Converters:ExpandedGroupConverter}}" ExpandDirection="Up">
<Expander.Header>
<Canvas>
**<TextBlock Text="{Binding} />**
</Canvas>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
At runtime, currently, the TextBlock text binds to the DataContext, which is a CollectionViewGroup, which makes sense as the grid is binding to a CollectionView wrapping our datasource.
However, the CollectionViewGroup is very limited and does not give us access to its containing ViewModel, where we are storing properties such where to position groups (we're gathering coordinates from the columns when we first layout the grid), and need to bind to them, so that we can, for example, show a total directly above/below given column in a group.
In a nutshell we're trying to access more than just the CollectionView object from within a DataTemplate that targets a GroupItem. Any input on how to do this (or if there is a better approach to get summed totals per columns to show in group total templates) appreciated.
EDIT: So far, a workaround is to have a "Parent ViewModel" property on our items, although this bloats the model, I wish there was a more direct way to do this.
CollectionViewGroup gives you access to all items contained in this group. if you want to access other information from within your template, you can try binding with RelativSource.
EDIT:
so if you have a Collection of ItemVM, and on top of this a CollectionViewGroup on ItemVM.GroupProperty. then you can access your 1st ItemVM within a group with
Binding={ Path = Items[0].AnyPropertyOnItemVM }
i think you will have to use a Converter if you want to calculate or do anything with the GroupItems

Resources