WPF Custom UserControl as a RadioToggleButton - wpf

I've created a custom UserControl that functions like a RadioButton but looks like a Toggle Button. The only issue I'm having is being able to set the Content property of the UserControl and have it appear in the ToggleButton. Here's what I've tried:
<UserControl.ContentTemplate>
<DataTemplate>
<RadioButton>
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Content="{TemplateBinding Content}"/>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</UserControl.ContentTemplate>
When I try to build this, I get the error: "Cannot find the static member 'ContentProperty' on the type 'Control'." I've been hung up on this all morning, and while I've tried to mimic a few examples, so far nothing has done the trick. Any ideas?

Got it:
<UserControl.ContentTemplate>
<DataTemplate>
<RadioButton Content="{TemplateBinding UserControl.Content}">
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsSelected, Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Content="{TemplateBinding UserControl.Content}"/>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
</DataTemplate>
</UserControl.ContentTemplate>

Related

WPF - Radio button control template binding "IsChecked" not working

I have a control template like below, and I want to get IsChecked property when user selects a radio button.
But when user select radio button "A" it's IsChecked property still show false. Why?
<ControlTemplate x:Key="RadioBtnTemplate" TargetType="{x:Type RadioButton}">
<Grid>
<StackPanel Margin="5">
<RadioButton Name="tempbtn" IsChecked="{TemplateBinding IsChecked}" FontFamily="Segoe UI" FontSize="18.667" Content="{TemplateBinding Content}" GroupName="{TemplateBinding GroupName}"/>
</StackPanel>
</Grid>
</ControlTemplate>
and I use this template:
<RadioButton GroupName="CG" x:Name="_rdoBtnA" Content="A" Template="{DynamicResource RadioBtnTemplate}" IsChecked="True"/>
<RadioButton GroupName="CG" x:Name="_rdoBtnB" Content="B" Template="{DynamicResource RadioBtnTemplate}" />
<RadioButton GroupName="CG" x:Name="_rdoBtnC" Content="C" Template="{DynamicResource RadioBtnTemplate}" />
If we take your example as is then you have two problems which cause the problems you are seeing.
Issue 1:
Firstly your design has created six not three <RadioButton> controls. The three in the <StackPanel> and then three that are created as part of the control template. All six radio buttons are now linked as part of the GroupName="CG" group.
As you know because they all belong to the same CG group only one of the six radio buttons can have the IsChecked property set to True. The three named controls _rdoBtnA, _rdoBtnB and _rdoBtnC are not even visible on the screen so they can never be set to True (and in the case of _rdoBtnA is promptly set to False from the XAML declared True the moment the template control is bound).
To resolve this situation, remove the GroupName="{TemplateBinding GroupName}" from the control template definition leaving only the three top level radio buttons in the group.
Issue 2: This is the issue I thought was the root of your problem to begin with. IsChecked={TemplateBinding IsChecked} is only OneWay binding and will not update the other way. To make the binding TwoWay you need to use the long-hand version of the binding definition, IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
The control template now becomes this by making those two changes.
<ControlTemplate x:Key="RadioBtnTemplate" TargetType="{x:Type RadioButton}">
<Grid>
<StackPanel Margin="5">
<RadioButton Name="tempbtn" IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" FontFamily="Segoe UI" FontSize="18.667" Content="{TemplateBinding Content}" />
</StackPanel>
</Grid>
</ControlTemplate>
you can use it this way:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<ControlTemplate x:Key="ButtonAsSwatchTemplate">
<Border x:Name="SwatchBorder" BorderThickness="1">
<Rectangle Fill="{TemplateBinding Property=Background}" Width="15" Height="15" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="ToggleButton.IsChecked" Value="True">
<Setter TargetName="SwatchBorder" Property="BorderBrush" Value="Yellow" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</StackPanel.Resources>
<RadioButton Template="{StaticResource ButtonAsSwatchTemplate}"
GroupName="CropGuidesColourRadioButtonGroup"
IsChecked="{Binding Checked}" Margin="2" Background="Red" />
<RadioButton Template="{StaticResource ButtonAsSwatchTemplate}"
GroupName="CropGuidesColourRadioButtonGroup"
IsChecked="{Binding Checked}" Margin="2" Background="Black" />
</StackPanel>
taken from StackOverFlow

Adding image to radiobutton that can act as Toggle button using wpf

I want to use 2 radiobuttons like a toggle button using WPF. I tried something like this:
<RadioButton Name="RightAnswer" Width="20">
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<Image Width="20"
Height="20"
VerticalAlignment="Center"
Source="{Binding Content,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}"
ToolTip="Right Answer" />
</ToggleButton>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
<RadioButton Name="WrongAnswer" Width="20">
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<Image Width="20"
Height="20"
VerticalAlignment="Center"
Source="{Binding Content,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}"
ToolTip="Wrong Answer" />
</ToggleButton>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
I could make this work but it shows blank buttons. I want to have buttons with Image in each of them and on checked i should bind it with ViewModel code.
Update: I added Images in each of them which now works well. I hoping to connect the press events to VM property.

To know the hosting Control of a Control

I have a custom control, its control template will be looks below.
<Style TargetType="local:CustomButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomButton">
<Grid>
<Border x:Name="CtrlBorder">
<StackPanel Orientation="Horizontal">
<TextBox Name="Tbox"
BorderThickness="1,1,0,1"
Text="{Binding TextBoxText,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<Button Width="20"
Background="#FFF0F0F0"
BorderThickness="0,1,1,1"
IsTabStop="False">
</Button>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In an event, i got the Tbox and i need to get the CustomButton with this Tbox.
Any idea on this?
Can get throught TemplatedParent property of that control.
Custom control will lie in Visual Tree as parent so FindAncestor will work here:
Text="{Binding TextBoxText,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:CustomButton},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />

Change DataTemplate style in ListBoxItem if the item is selected

I have a listbox with an expander inside the ItemTemplate. I managed to bind the expander's IsExpanded property to the ListBoxItem's IsSelected property ok. Now I want to apply a style to the ListBoxItem's content also bound to the IsSelected property.
<ListBox.ItemTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
What I want to do is somehow set the style of the "myBorder" Border to "NotSelectedBorderStyle" for unselected ListBoxItems, and to "SelectedBorderStyle" for the SelectedItem (ListBox with single selection).
FYI, the styles define background, border and that kind of stuff, just to make the clear which item is selected, nothing fancy in these styles.
I tried the accepted answer here but if I completely switch styles I loose the nice expanding animation my DXExpander has.
I guess there must be some solution using triggers, but I can't just hit the right spot.
Finally I got it, I'm posting it here in the hope that this will save someone else time and pain :-P
This code does some additional things: the EventSetter and the corresponding Handler method are there to capture clicks to the elements inside the DataTemplate, in order to select the ListBoxItem which contains the element (if you don't you might type text inside an item, while a different one is selected).
The inner Border ("myBorder") is just a container for the stackpanels, I had to wrap everything inside another border ("backgroundBorder") which gets the style changed when the ListBoxItem gets selected.
<Style x:Key="FocusedContainer" TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="LightGray"/>
<EventSetter Event="GotKeyboardFocus" Handler="OnListBoxItemContainerFocused" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="backgroundBorder" Width="Auto" Style="{StaticResource NotSelectedBorderStyle}">
<ContentPresenter Content="{TemplateBinding Content}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<Border Name="myBorder">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Description}" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="Date:"/>
<TextBlock Text="{Binding Date}"/>
</StackPanel>
<dx:DXExpander Name="expanderDetails"
IsExpanded="{Binding Mode=TwoWay, Path=IsSelected,
RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Count:"/>
<TextBlock Text="{Binding Count}"/>
</StackPanel>
</dx:DXExpander>
</StackPanel>
</Border>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="backgroundBorder" Property="Style" Value="{StaticResource SelectedBorderStyle}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then I set the ItemContainerStyle in my ListBox to the above style:
<ListBox Background="#7FFFFFFF" HorizontalContentAlignment="Stretch"
ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"
ItemContainerStyle="{StaticResource FocusedContainer}"/>
To finish, the code behind for the GotKeyBoardFocus handler:
private void OnListBoxItemContainerFocused(object sender, RoutedEventArgs e)
{
(sender as ListBoxItem).IsSelected = true;
}
A mess in code, but pretty neat in the UI. Hope this helps someone!

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/

Resources