Vertical alignment of ContentControl in styled ListBox - wpf

I am using this answer to style a ListBox as RadioButton in order to simplify my MVVM code by driving the selection of the checked radio button via an enum - rather than having a bunch of bools that I have to manually map back and forth to the enum.
This works really well when the ListBoxItem content is a simple line of text. The radio button aligns with the text and all is happy. But when I change the content to be a UserControl, the radio button for that choice gets rendered at the vertical mid-point of the UserControl rather than at the top (where I want it).
Here is some code and an image that better explains what I am trying to do (Note that a bunch of stuff has been left out for clarity):
The UserControl that I am inserting as content into one of the choices
<UserControl x:Class="TestCtl">
<StackPanel Orientation="Vertical" >
<Label Margin="-5,0,0,0" Content="Choice #2"/>
<CheckBox Margin="10,0,0,5">Option 1</CheckBox>
<CheckBox Margin="10,0,0,5">Option 2</CheckBox>
<CheckBox Margin="10,0,0,0">Option 3</CheckBox>
</StackPanel>
</UserControl>
The ListBox (with the aforementioned style defined elsewhere)
<StackPanel Orientation="Vertical">
<ListBox SelectedValuePath="Tag"
Style="{StaticResource RadioButtonList}"
SelectedValue="{Binding Blah Blah"}>
<ListBoxItem Tag="Choice1" Content="Choice #1" />
<ListBoxItem Tag="Choice2">
<ContentControl>
<subf:TestCtl />
</ContentControl>
</ListBoxItem>
<ListBoxItem Tag="Choice3" Content="Choice #3"/>
<ListBoxItem Tag="Choice4" Content="Choice #4" />
</ListBox>
<ComboBox blah blah/>
</StackPanel>
What it looks like when rendered:
I have tried setting both the VerticalAlignment and VerticalContentAlignment as well as playing with Margin and Padding in every location I can think of in both my xaml code and the style that I linked to, but no matter what I set I can't get the radio button and user control to align at their tops.
Is there anyway to achieve what I want by modifying either the style I am using or my code? Or am I just flat out doing this wrong?

In the RabioButtonList style change this:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="5" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border BorderThickness="0" Background="Transparent">
<!-- CHANGE THIS -->
<StackPanel Orientation="Horizontal">
<RadioButton
Focusable="False"
IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}"/>
<ContentPresenter />
</StackPanel>
<!------------------>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
In the user control set the label Padding to 5,0,0,0 (Thanks to mm8)
<StackPanel Orientation="Vertical" >
<Label Margin="-5,0,0,0" Content="Choice #2" Padding="5,0,0,0"/>
<CheckBox Margin="10,0,0,5">Option 1</CheckBox>
<CheckBox Margin="10,0,0,5">Option 2</CheckBox>
<CheckBox Margin="10,0,0,0">Option 3</CheckBox>
</StackPanel>

Set the Padding of the Label to 0:
<ListBoxItem Tag="Choice1" Content="Choice #1" />
<ListBoxItem Tag="Choice2">
<UserControl>
<StackPanel Orientation="Vertical" >
<Label Content="Choice #2" Padding="0"/>
<CheckBox Margin="10,0,0,5">Option 1</CheckBox>
<CheckBox Margin="10,0,0,5">Option 2</CheckBox>
<CheckBox Margin="10,0,0,0">Option 3</CheckBox>
</StackPanel>
</UserControl>
</ListBoxItem>
<ListBoxItem Tag="Choice3" Content="Choice #3"/>
<ListBoxItem Tag="Choice4" Content="Choice #4" />
This should fix the vertical alignment issue. But you shouldn't have to use a ListBox in the first place to be able to bind a RadioButton to a single source property:
WPF + MVVM + RadioButton : Handle binding with single property

Related

Wpf change the background colour of an expander's header only

I have looked at many questions/answers but couldn't find what I was exactly looking for,
I am trying to change the background colour of the expander's header only and not have the same colour continue for the content within the expander. Preferably within xaml but a vb.net solution would suffice.
(Any comments or suggestions will be helpful)
If this is a duplicated question please direct me to the answer and leave the question open to help others avoid the same issue in the future!
Thanks.
I am not sure whether this is what you are exactly looking for, but you could change the header background by simply doing that:
<Expander VerticalAlignment="Center">
<Expander.Header>
<Grid Background="LightBlue">
<TextBlock Text="Expander Header"/>
</Grid>
</Expander.Header>
<StackPanel>
<TextBlock Text="Cotent"></TextBlock>
</StackPanel>
</Expander>
Or you could override the default Expander's Header DataTemplate by using HeaderTemplate
<Window.Resources>
<DataTemplate x:Key="HeaderText">
<Border Height="25" Background="LightBlue">
<TextBlock Text="{Binding}"
Margin="4 0"
VerticalAlignment="Center"
Foreground="White"
FontSize="11"
FontWeight="Normal"
/>
</Border>
</DataTemplate>
<Style TargetType="{x:Type Expander}">
<Setter Property="HeaderTemplate" Value="{StaticResource HeaderText}"/>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<Expander VerticalAlignment="Center" Header="Expander Header">
<StackPanel>
<TextBlock Text="Cotent"></TextBlock>
</StackPanel>
</Expander>
</Grid>
I had a lot of problems with getting the header background set.
I found the easiets weay to do it was to simply make a coloured rectangle and put it behind the expander. (use Margin and Height to make it fit)
<Rectangle Grid.Row="1" Grid.Column="0" Fill="LightBlue" Height="33" Margin="0,0,0,-35" />
or use a border if you want rounded corners:
<Border CornerRadius="15" Height="33" Margin="0,0,0,-35" Background="LightBlue" />

WPF - Bug allowing multiple selection in a radiobutton group

I have multiple ratio buttons in itemscontrols and datatemplates binded to database data in a MVVM / prism application. Each group of radio buttons is grouped with a name accordingly so that they are seprate groups.
The problem I'm having (and goes against convention of radio buttons) is that you can select multiple options within the group. Not all options allow multiple selection. Some behave as they should others do not. On inspection through snoop all radio buttons are part of the same group but with multiple buttons reporting true to IsChecked.
Any ideas?
Thanks
EDIT - Code
XAML
<StackPanel Grid.Column="0" Margin="10,0,0,10">
<TextBlock Margin="5,5,0,5"
FontSize="16"
FontWeight="Bold"
Foreground="{Binding Path=ThemeBackground}"
Text="From" />
<ItemsControl ItemsSource="{Binding Path=InternetItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Margin="5"
Content="{Binding Path=Title}"
GroupName="InternetFrom"
IsChecked="{Binding Path=IsSelected}"
IsEnabled="{Binding Path=IsEnabled}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
View Model
public ObservableCollection<Item> InternetItems
{
get
{
return
new ObservableCollection<Item>(
_items.Where(x => x.Category == Category.InternetFrom).OrderBy(x => x.DisplayOrder));
}
}
Edit -
Problem resolved. Code behind was initiating a new observable collection each time a radio button was selected leading to multiple datacontexts regardless of the groupnames of the radio buttons being the same
RadioButton controls are mutually excusive if they share the same container (e.g. anything derives from Panel, or ContentControl). In your case, each item generated in your ItemsControl is a separate container thus the buttons are not automatically mutually exclusive.
For example:
If your ItemsControl is setup like this, the buttons are mutually exclusive:
<ItemsControl>
<RadioButton Content="1" />
<RadioButton Content="2" />
<RadioButton Content="3" />
<RadioButton Content="4" />
</ItemsControl>
But this is not:
<ItemsControl>
<Grid>
<RadioButton Content="1" />
</Grid>
<Grid>
<RadioButton Content="2" />
</Grid>
<Grid>
<RadioButton Content="3" />
</Grid>
<Grid>
<RadioButton Content="4" />
</Grid>
</ItemsControl>
As Dean said, assigning the same GroupName property will solve your problem.
<ItemsControl>
<Grid>
<RadioButton Content="1"
GroupName="Group1" />
</Grid>
<Grid>
<RadioButton Content="2"
GroupName="Group1" />
</Grid>
<Grid>
<RadioButton Content="3"
GroupName="Group1" />
</Grid>
<Grid>
<RadioButton Content="4"
GroupName="Group1" />
</Grid>
</ItemsControl>
Edit
If you have multiple ItemsControls, you can simply set a different GroupName for RadioButtons in each ItemsControl. An in-scope default style comes in handy in this case:
<StackPanel>
<ItemsControl>
<ItemsControl.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="GroupName"
Value="Group1" />
</Style>
</ItemsControl.Resources>
<Grid>
<RadioButton Content="1" />
</Grid>
<Grid>
<RadioButton Content="2" />
</Grid>
<Grid>
<RadioButton Content="3" />
</Grid>
<Grid>
<RadioButton Content="4" />
</Grid>
</ItemsControl>
<ItemsControl>
<ItemsControl.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="GroupName"
Value="Group2" />
</Style>
</ItemsControl.Resources>
<Grid>
<RadioButton Content="1" />
</Grid>
<Grid>
<RadioButton Content="2" />
</Grid>
<Grid>
<RadioButton Content="3" />
</Grid>
<Grid>
<RadioButton Content="4" />
</Grid>
</ItemsControl>
</StackPanel>
You're doing something wrong, RadioButtons within a same group are mutually exclusive. If you use GroupName Property to assign two or more RadioButtons to a group you will be able to select only one RadioButton from that group.
RadioButton.GroupName Property
Gets or sets the name that specifies which RadioButton controls are
mutually exclusive.
The user can choose one RadioButton in each group.
Do you really think you discovered such a fundamental bug in the .NET Framework that no one reported yet and was not fixed already?

Binding a command defined in a datatemplate

I know there are few answers on this topic. But none of them was working in my case.
I have a ListView with a style and an ItemContainerStyle. In the ItemContainer Style, I define some triggers in order to use a different DataTemplate depending if the item in the list is selected or not. Then, finally in the Datatemplate I have a context menu with a command. The problem is how to bind the command to the viewmodel.
This is the ListView:
<ListView
x:Name="lstPersons"
Grid.Row="1"
Style="{StaticResource ListViewStyle}"
ItemContainerStyle="{StaticResource ItemContainerStyle}"
DataContext="{Binding}"
ItemsSource="{Binding Path=Persons}"
Tag="{Binding}"
SelectedItem="{Binding Path=SelectedPerson, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</ListView>
and these are the styles, datatemplates and contextmenu (defined in a resource dictionary).
The commands in the context menu do not work....:
<ContextMenu x:Key="SelectedItemContextMenu">
<MenuItem
Header="Do Something"
Command="{Binding Path=DataContext.DoSomethingCmd, ElementName=LayoutRoot}">
</MenuItem>
<MenuItem
Header="Do Something"
Command="{Binding PlacementTarget.Tag.DoSomethingCmd, RelativeSource={RelativeSource AncestorType=ContextMenu}}">
</MenuItem>
</ContextMenu>
<DataTemplate
x:Key="ItemTemplate">
<Canvas
Margin="4"
Width="60"
Height="60"
Background="LightGray">
<TextBlock
Foreground="Black"
Margin="2 0 0 0"
Opacity="0.5"
FontFamily="Segoe UI"
Text="{Binding Path=FirstName}" />
</Canvas>
</DataTemplate>
<DataTemplate
x:Key="ItemSelectedTemplate">
<Grid>
<Border
BorderBrush="Black"
BorderThickness="1"
Margin="3"
ContextMenu="{DynamicResource SelectedItemContextMenu}">
<Canvas
Width="60"
Height="60"
Background="LightBlue">
<TextBlock
Foreground="Black"
Margin="2 0 0 0"
Opacity="0.5"
FontFamily="Segoe UI"
Text="{Binding Path=FirstName}" />
</Canvas>
</Border>
</Grid>
</DataTemplate>
<!--style of the listviewitem-->
<Style
TargetType="{x:Type ListViewItem}"
x:Key="ItemContainerStyle">
<Setter
Property="ContentTemplate"
Value="{StaticResource ItemTemplate}" />
<Style.Triggers>
<Trigger
Property="IsSelected"
Value="True">
<Setter
Property="ContentTemplate"
Value="{StaticResource ItemSelectedTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
<!--style of the listview-->
<Style
TargetType="{x:Type ListBox}"
x:Key="ListViewStyle">
<Setter
Property="Template">
<Setter.Value>
<ControlTemplate
TargetType="{x:Type ListBox}">
<Grid>
<Border>
<ScrollViewer
Focusable="false">
<WrapPanel
IsItemsHost="True"
Orientation="Horizontal"
Width="{Binding (FrameworkElement.ActualWidth), RelativeSource={RelativeSource AncestorType=ScrollContentPresenter}}"/>
</ScrollViewer>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Your ContextMenu is used inside a data template. I will be put in a different name scope of "LayoutRoot" and ElementName binding won't work. Also, the PlacementTarget of your context menu is the Border, and you've not setup any Tag on it. So the second command won't work either.
It looks like you are implement the commands on the ListBox level (or LayoutRoot?). It might be easier to put your context menu on the ListBox, and use ListBox.SelectedItem to find the current selection and apply your logic on it.
You can use RelativeSource:
<ContextMenu x:Key="SelectedItemContextMenu">
<MenuItem
Header="Do Something"
Command="{Binding Path=DataContext.DoSomethingCmd, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}">
</MenuItem>
</ContextMenu>
You should probably be using RoutedCommands instead of VM commands in this case. You would bind the RoutedCommand to the ContextMenu, and since you only need static object references for that, finding them shouldn't be a problem. Then you'd set up appropriate CommandBindings on the controls that should handle the commands (either ListView or ListViewItem, depending on whether you want the List-ViewModel or the Item-ViewModel to handle the command). These controls will know their ViewModels, so binding to them will not be a problem there. Through the process of Command Routing, which is built-in in WPF, the context menu will find the proper target for its command automatically.
For guidance on how to set up CommandBindings in a MVVM-friendly way, you might want to refer to http://wpfglue.wordpress.com/2012/05/07/commanding-binding-controls-to-methods/

Borderless ImageButtons in WrapPanel

I am attempting to create a WrapPanel with seamless ImageButtons containing Artwork. I put together the following ContentTemplate in the hopes that it would provide the seamless look required; however a thin white-line remained around each of the buttons. Can anyone steer me in the right direction?
<Button.ContentTemplate>
<DataTemplate DataType="{x:Type local:ArtInfo}">
<Border Name="border" BorderThickness="0" BorderBrush="blue" Height="280" Width="250" Background="#262c40">
<StackPanel>
<Grid>
<Grid.Resources>
<local:MyConverter x:Key="MyConverter"></local:MyConverter>
<ObjectDataProvider x:Key="Properties.Settings" ObjectType="{x:Type lcl:Properties.Settings}" />
</Grid.Resources>
<Image Name="ArtImage" Margin="10,15,0,0" Height="195" Width="195" VerticalAlignment="Top" >
<Image.Source>
<Binding Path="ArtImage"/>
</Image.Source>
</Image>
</Grid>
<TextBlock Text="{Binding Path=ArtClass}" Margin="10,-17,0,0" FontSize="11" Foreground="white" />
<TextBlock Text="{Binding Path=Student}" Margin="10,0,0,0" FontSize="11" Foreground="white" />
<TextBlock Text="1998" Margin="10,0,0,0" FontSize="11" Foreground="white" />
</StackPanel>
</Border>
</DataTemplate>
</Button.ContentTemplate>
The ContentTemplate tells WPF how to display the content within the Button -- the Button chrome (such as the border and background) remains, and the templated content is displayed within and over that chrome.
You want to replace the entire appearance of the Button, border and all, rather than just customising how its content is displayed. To do this, you need to use the Template property instead. The value of Button.Template is a ControlTemplate rather than a DataTemplate. Within that ControlTemplate, you can use the ContentPresenter to display the "data-templated" content.
In your case, since your DataTemplate is doing all the work, you could get away with a raw ContentPresenter as your template:
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter />
</ControlTemplate>
</Button.Template>
However, if all your buttons are using the same background, you could move this into the ControlTemplate:
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderBrush="Blue" ...>
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
You could then remove the Border from the DataTemplate. This would really only matter if you were planning to reuse the same Button.Template with other content templates and wanted to keep the appearance of the Button consistent across different kinds of content.
create a usercontrol, put the botton & image in a grid.
<Grid>
<Image Source="icon.png" Panel.ZIndex="1" />
<Button
Panel.ZIndex="2"
FocusVisualStyle="{x:Null}"
Background="Transparent"/>
</Grid>

Why does this WPF button stretch across the window?

The button below always expands to be as wide as the TextBlock. I've tried StackPanel, DockPanel, Width="Auto", etc.
How can I make the button expand to the size of its own text (as in HTML) and not to the size of text in its environement?
<DockPanel HorizontalAlignment="Left">
<Button x:Name="ButtonFavorite"
DockPanel.Dock="Top"
Content="Customers"
Margin="10"
Width="Auto"
Click="ButtonFavorite_Click">
</Button>
<TextBlock DockPanel.Dock="Top" Text="this is a long text which makes the button stretch across the window, if this text is just a couple words, the button will be smaller, and this drives me up the wall" Margin="10" TextWrapping="Wrap" />
</DockPanel>
ANSWER:
Thanks Greg, that did it. Here is the full XAML that works now, you can right-click the button to change its Content so see that the button expands and contracts appropriately.
<Window x:Class="Test3784234.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<DockPanel HorizontalAlignment="Left">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" >
<Button x:Name="ButtonFavorite"
Padding="5"
Cursor="Hand"
DockPanel.Dock="Top"
Content="Customers"
Margin="10"
Click="ButtonFavorite_Click">
<Button.ContextMenu>
<ContextMenu>
<MenuItem x:Name="menuItemReports" Header="Reports" Click="MenuItem_Click" />
<MenuItem x:Name="menuItemContracts" Header="Contracts" Click="MenuItem_Click"/>
<MenuItem x:Name="menuItemCustomers" Header="Customers" Click="MenuItem_Click" />
<MenuItem x:Name="menuItemDocumentation" Header="Documentation Creation Instructions" Click="MenuItem_Click" />
<MenuItem x:Name="menuItemEmail" Header="E-Mail" Click="MenuItem_Click" />
</ContextMenu>
</Button.ContextMenu>
</Button>
</StackPanel>
<TextBlock x:Name="TheMessage" DockPanel.Dock="Top" Text="Right-click the 'favorites' button to change its function." Margin="10" TextWrapping="Wrap"/>
</DockPanel>
</Window>
All you need to do is set the HorizontalAlignment property on your button. It defaults to stretch therefore filling the available space.
<Button x:Name="ButtonFavorite"
HorizontalAlignment="Left"
Content="Customers"
Margin="10"
Width="Auto"
Click="ButtonFavorite_Click">
Regarding your annoyance at the sizing of buttons, this is something that seems to be targeted at the designer in the designer/developer workflow, while you're clearly working on the developer portion. For the sake of development, I always apply a few styles in my App.xaml to ensure somewhat better button sizing. For example, in the application tag in your app.xaml file:
<Application.Resources>
<Style TargetType="Button">
<Setter Property="MinWidth" Value="60" />
<Setter Property="MinHeight" Value="23" />
<Setter Property="Margin" Value="3" />
</Style>
</Application.Resources>
Regarding your actual question:
The problem is that your DockPanel is stretching to the width of the text and the button will naturally expand to fill the available area. If you want the quick and dirty solution you can do something like:
<DockPanel HorizontalAlignment="Left">
<Button x:Name="ButtonFavorite"
DockPanel.Dock="Top"
Content="Customers"
Margin="10"
Width="Auto"
MaxWidth="100"
Click="ButtonFavorite_Click">
</Button>
</DockPanel>
Note the MaxWidth. If you want a more composable result, isolate your button in another panel. (I'm using a stackpanel because I believe someone else already used a grid in their example):
<DockPanel HorizontalAlignment="Left">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<Button x:Name="ButtonFavorite"
Content="Customers"
Margin="10"
Width="Auto"
Click="ButtonFavorite_Click" />
</StackPanel>
<TextBlock DockPanel.Dock="Top" Text="this is a long text which makes the button stretch across the window, if this text is just a couple words, the button will be smaller, and this drives me up the wall" Margin="10" TextWrapping="Wrap" />
</DockPanel>
I like the StackPanel in this case because I find myself using it to create the horizontal "bar" of buttons along the bottom of a Form- err- Window in the right corner.
You could try isolating the button from the main panel by putting it in another panel.
<DockPanel HorizontalAlignment="Left">
<Grid DockPanel.Dock="Top">
<Button x:Name="ButtonFavorite"
Content="Customers"
Margin="10"
Width="Auto"
Click="ButtonFavorite_Click">
</Button>
</Grid>
<TextBlock DockPanel.Dock="Top" Text="this is a long text which makes the button stretch across the window, if this text is just a couple words, the button will be smaller, and this drives me up the wall" Margin="10" TextWrapping="Wrap" />
</DockPanel>
Can you place them in a two column Grid with the button spanning just one column and the text spanning two columns?
Here's an example using a Grid layout versus a DockPanel. The idea is to have 2 columns and 2 rows. Put the Button it a single cell and make that row/column pair auto-sizing. Then put the TextBox into the second row and have it span both of the columns. This will essentially make the top-right cell just filler space and will achieve the behavior you're looking for.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Button
x:Name="ButtonFavorite"
Grid.Column="0"
Grid.Row="0"
Content="Customers"
Margin="10"
Width="Auto"
Click="ButtonFavorite_Click">
</Button>
<TextBlock
Grid.Column="0"
Grid.ColumnSpan="2"
Grid.Row="1"
Margin="10"
TextWrapping="Wrap"
Text="this is a long text which makes the button stretch across the window, if this text is just a couple words, the button will be smaller, and this drives me up the wall" />
</Grid>
As another method to do this: You could change the button's template so it's essentially wrapped in a centered StackPanel. Something like this:
<Button Content="Back">
<Button.Template>
<ControlTemplate>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Content="{TemplateBinding Content}"></Button>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
Or you could add a style to app.xaml (or any other place where you're storing your global styles) like this:
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Style="{x:Null}" Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type Button}} }" FontWeight="Bold" Padding="5"></Button>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that it's important to include the Style="{x:Null}" attribute on the button within the template if adding to the global styles, otherwise you'll get an infinite loop when it comes to rendering the button.

Resources