Apply the focus rectangle to a child element - wpf

In a control I'm currently working on, when an item is focused, the default focus rectangle spans the entire row and all of its visible child items. I know how to hide that. But I still want such a focus indicator when the item has the keyboard focus. I've read about the IsKeyboardFocused property but that is true also when the item was clicked by the mouse. So I guess I need to use the FocusVisualStyle somehow. But I can't figure out how.
Here's what the default focus looks like:
And this is what it should like:
Here's my XAML code for the control template:
<Border ...>
<ContentPresenter FocusManager.IsFocusScope="True"
Content="{TemplateBinding HeaderedContentControl.Header}"
ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
ContentStringFormat="{TemplateBinding HeaderedItemsControl.HeaderStringFormat}"
ContentSource="Header"
Name="PART_Header" .../>
</Border>
<!-- Additional border glare inside the item -->
<Border BorderThickness="1" BorderBrush="#80ffffff" Margin="1"
SnapsToDevicePixels="True" CornerRadius="2"/>
<!-- Focus rectangle inside the item -->
<Rectangle StrokeDashArray="1 2" StrokeThickness="1" Stroke="Black"
SnapsToDevicePixels="True" Margin="2"
Visibility="Hidden" Name="FocusRectangle"
FocusVisualStyle="{StaticResource FocusStyle}"/>
There is already a focus rectangle in my XAML that is invisible by default. With FocusVisualStyle or anything, it shall be made visible. But I didn't manage to do that. It's either visible at any focus, or never.

I have found a workaround for the issue. It looks the same to me, but I'm not completely sure whether it's the correct way to do it. I'm using the FocusRectangle from above and care for showing and hiding it myself.
Here's the trigger that manages the focus rectangle visibility:
<!-- Show the focus rectangle when the item is focused -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Controls:TreeViewExItem.IsKeyboardMode" Value="True"/>
<Condition Property="Controls:TreeViewExItem.IsFocused" Value="True"/>
</MultiTrigger.Conditions>
<Setter TargetName="FocusRectangle" Property="Visibility" Value="Visible"/>
</MultiTrigger>
Then I have added a new property to the TreeViewExItem that indicates whether the last input came from the mouse or the keyboard. This could possibly be extended to touch or stylus, but I don't have such devices to test.
public static DependencyProperty IsKeyboardModeProperty =
DependencyProperty.Register(
"IsKeyboardMode",
typeof(bool),
typeof(TreeViewExItem),
new FrameworkPropertyMetadata(false, null));
public bool IsKeyboardMode
{
get
{
return (bool) GetValue(IsKeyboardModeProperty);
}
set
{
SetValue(IsKeyboardModeProperty, value);
}
}
This property is passed to each item from the parent control through binding:
<!-- Pass on the TreeViewEx' IsKeyboardMode value to each item because
we couldn't access it otherwise in the triggers -->
<Setter Property="IsKeyboardMode"
Value="{Binding (Controls:TreeViewEx.IsKeyboardMode),
RelativeSource={RelativeSource
AncestorType={x:Type Controls:TreeViewEx}}, Mode=OneWay}" />
The same IsKeyboardMode property is added to the TreeViewEx parent control and here comes my magic:
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (!IsKeyboardMode)
{
IsKeyboardMode = true;
//Debug.WriteLine("Changing to keyboard mode from PreviewKeyDown");
}
}
protected override void OnPreviewKeyUp(KeyEventArgs e)
{
base.OnPreviewKeyDown(e);
if (!IsKeyboardMode)
{
IsKeyboardMode = true;
//Debug.WriteLine("Changing to keyboard mode from PreviewKeyUp");
}
}
protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
{
base.OnPreviewMouseDown(e);
if (IsKeyboardMode)
{
IsKeyboardMode = false;
//Debug.WriteLine("Changing to mouse mode");
}
}
This reacts on the preview events of keyboard and mouse to set the appropriate input mode. Only if the last input came from a keyboard, the focus rectangle is visible.

Related

How to handle attached properties events?

I created an expander style that contains a checkbox in its header. The checkbox state is bound to an attached property:
<Style TargetType="{x:Type Expander}" x:Key="MyCheckboxExpander">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
(...)
<CheckBox x:Name="ExpanderHeaderChk" VerticalAlignment="Center" Margin="4,0,0,2"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(my:AP.IsChecked)}" />
(...)
I my view, inside the expander I have a stackpanel with a ComboBox.
Whenever the user checks the expander's checkbox, I wan't that the combobox gets the first item selected, on the oher hand whenever the user unchecks it, I wan't that the selecteditem of the combobox be null.
How can I accomplish this? I'm following the MVVM pattern, but since this is more a matter of the view, I'm open to code-behind suggestions.
Well, I think your design is not optimal. You see, you are trying to change the semantics of the Expander. The real expander doesn't have the semantics with additional checkbox, so the control you are creating is not an Expander any more.
I would suggest that you switch to a user control (or maybe a custom control, look at your semantics), and expose the needed event in your control's class. The XAML for the user control should be perhaps an expander with a checkbox.
Edit: example with UserControl (not tested)
(XAML)
<UserControl x:Class="namespace:MyCheckboxExpander">
<Expander>
...
<Checkbox x:Name="cb"/>
...
</Expander>
</UserControl>
(code-behind)
public class MyCheckboxExpander : UserControl
{
MyCheckboxExpander()
{
InitializeComponent();
cb.Check += OnCheck;
}
void OnCheck(object sender, whatever2 args)
{
if (CheckboxTriggered != null)
CheckboxTriggered(new EventArgs<whatever>);
}
public event EventArgs<whatever> CheckboxTriggered;
}
WPF is so powerfull framework, that you can solve you problem just using next style for Expander:
<Style x:Key="myExpanderStyle" TargetType="{x:Type Expander}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<StackPanel>
<CheckBox x:Name="PART_CheckBox" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding Content}" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Setter TargetName="PART_ComboBox" Property="SelectedIndex" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
SAMPLE:
<Expander Style="{StaticResource myExpanderStyle}">
<x:Array Type="sys:String">
<sys:String>1</sys:String>
<sys:String>2</sys:String>
<sys:String>3</sys:String>
</x:Array>
</Expander>
Just XAML! I like XAML declarativity.
But from MVVM perspective, this approach has one disadvantage - I can't cover this case with unit tests. So, I would prefer:
create view model with properties: IsChecked(bound to CheckBox),
SelectedItem(bound to ComboBox) and Source(ItemsSource for ComboBox) -
abstration of my real view without any references on controls;
write a logic in view model that set or unset SelectedItem depending
on IsChecked property;
cover that logic with unit test (yep, you can
even start with this point, if you like test first approach).
I followed the suggestion provided by #Baboon and I created a custom control with a routed event named CheckedChanged, this way I can access it through the view's xaml and code-behind:
[TemplatePart(Name = "PART_Expander", Type = typeof(Expander))]
[TemplatePart(Name = "PART_CheckBox", Type = typeof(CheckBox))]
public class MyCustomExpander : Expander
{
static MyCustomExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomExpander), new FrameworkPropertyMetadata(typeof(MyCustomExpander)));
}
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(MyCustomExpander),
new UIPropertyMetadata(false));
#region Events
private CheckBox chkExpander = new CheckBox();
public CheckBox ChkExpander { get { return chkExpander; } private set { chkExpander = value; } }
public static readonly RoutedEvent CheckedChangedEvent = EventManager.RegisterRoutedEvent("ExtraButtonClick",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(MyCustomExpander));
public event RoutedEventHandler CheckedChanged
{
add { AddHandler(CheckedChangedEvent, value); }
remove { RemoveHandler(CheckedChangedEvent, value); }
}
void OnCheckedChanged(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(CheckedChangedEvent, this));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
CheckBox chk = base.GetTemplateChild("PART_CheckBox") as CheckBox;
if (chk != null)
{
chk.Checked += new RoutedEventHandler(OnCheckedChanged);
chk.Unchecked += new RoutedEventHandler(OnCheckedChanged);
}
}
#endregion
}
I want to thank to #Baboon and #Vlad for their help.

QStackedLayout equivalent in WPF

I'm a quite experienced Qt programmer and I used QStackedLayout a lot to show different widgets in the main window. Can someone please point me to an equivalent construct in WPF: Is there such a thing like QStackedLayout? If not, how is this pattern used in WPF?
Basically I have a WPF Ribbon Application and if the Ribbon Group is switched the corresponding "widget" / XAML should be displayed in the remaining area ("content").
Thanks, dude.
There isn't a native panel or control that would do that, but you could leverage the TabControl to accomplish it. You'd need to use a custom Style, though like so:
<Style x:Key="NoTabsTabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
KeyboardNavigation.TabNavigation="Local"
KeyboardNavigation.DirectionalNavigation="Contained">
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then use it like:
<TabControl Style="{StaticResource NoTabsTabControlStyle}">
<TabItem Content="One" />
<TabItem Content="Two" />
</TabControl>
Then to display one set of content, you'd set SelectedIndex on the TabControl.
A bit late for topic starter but may be of some help to people who comes here searching for WPF version of QStackedLayout, like me.
I used the very simplified implementation of WPF layout example, throwing out virtually all things layout.
The component is based on StackLayout to allow for simple visual design, in design time it just behaves like normal stack panel.
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;
namespace org.tequilacat.stacklayout {
/// <summary>
/// QStackedLayout implementation for WPF
/// only one child is displayed extended to the panel size.
/// In design time it behaves like stack panel
/// </summary>
public class StackLayoutPanel : StackPanel {
private bool isDesignTime() {
return System.ComponentModel.DesignerProperties.GetIsInDesignMode(this);
}
private bool useBaseBehaviour() {
return isDesignTime();
}
// in runtime just return the given arg
protected override Size MeasureOverride(Size availableSize) {
if (useBaseBehaviour()) {
return base.MeasureOverride(availableSize);
}
return availableSize;
}
// in runtime arrange all children to the given arg
protected override Size ArrangeOverride(Size finalSize) {
if (useBaseBehaviour()) {
return base.ArrangeOverride(finalSize);
}
foreach (UIElement child in InternalChildren) {
child.Arrange(new Rect(finalSize));
}
return finalSize;
}
}
}
The XAML is
<Window ... xmlns:uilib="clr-namespace:org.tequilacat.stacklayout">
<uilib:StackLayoutPanel >
<StackPanel Name="projectPropertyPanel"> ... </StackPanel>
<StackPanel Name="configurationPanel"> ... </StackPanel>
<StackPanel Name="casePanel"> ... </StackPanel>
</uilib:StackLayoutPanel>
In Run time the visible component is chosen via Visibility property (here depends on my business logic, uiState can take 3 values activating one of panels). It's very basic, one can implement own CurrentPage property or so, I just kept it simple:
projectPropertyPanel.Visibility = (uiState == UiState.ProjectProperties) ?
Visibility.Visible : Visibility.Collapsed;
configurationPanel.Visibility = (uiState == UiState.ConfigurationSelected) ?
Visibility.Visible : Visibility.Collapsed;
casePanel.Visibility = (uiState == UiState.CaseSelected) ?
Visibility.Visible : Visibility.Collapsed;

Attached behavior binding to element in controltemplate

I am adding an attached behaviour to a slider which will cause it to scroll some content when the thumb is dragged and held over a specific region. (Can't use a straightforward IsMouseOver trigger as the Slider Thumb has MouseCapture.)
The behaviour has 3 properties:
#region IsScrollHoverProperty
public static readonly DependencyProperty IsScrollHoverProperty = DependencyProperty.RegisterAttached(
"IsScrollHover",
typeof(Boolean),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(false));
#endregion
#region ScrollLeftRectProperty
public static readonly DependencyProperty ScrollLeftRectProperty = DependencyProperty.RegisterAttached(
"ScrollLeftRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
#region ScrollRightRectProperty
public static readonly DependencyProperty ScrollRightRectProperty = DependencyProperty.RegisterAttached(
"ScrollRightRect",
typeof(Rectangle),
typeof(ScrollHoverAreaBehaviour),
new UIPropertyMetadata(null));
#endregion
The IsScrollHoverProperty is being set to true when the user drags the slider, this is all done in the Slider's ControlTemplates.Triggers, and works correctly.
When it's set to true the callback is going to hook PreviewMouseEnterHandlers into the two Rectangles to detect when the mouse enters them.
The Rectangles in question are also defined in the Slider's controltemplate thusly:
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Left" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollLeftRect"/>
</StackPanel>
<StackPanel Grid.Row="0" Grid.RowSpan="3" HorizontalAlignment="Right" Orientation="Horizontal">
<Rectangle Width="40" Fill="#AAAAAAAA" Name="ScrollRightRect"/>
</StackPanel>
The problem I have is binding these Rectangles to the attached ScrollRightRect and ScrollLeftRect Properties. I have tried a few things and suspect I have made a stupid binding error or am trying to do something not allowed. I am currently binding them in the controltemplate.triggers as follows:
<Trigger Property="local:ScrollHoverAreaBehaviour.IsScrollHover" Value="False">
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollLeftRect" Value="{Binding ElementName=ScrollLeftRect}"/>
<Setter Property="local:ScrollHoverAreaBehaviour.ScrollRightRect" Value="{Binding ElementName=ScrollRightRect}"/>
<Setter TargetName="ScrollLeftRect" Property="Fill" Value="Red"/>
<Setter TargetName="ScrollRightRect" Property="Fill" Value="Red"/>
</Trigger>
I know this Trigger is being tripped as the rectangles fill Red as expected.
Can anyone spot what I'm doing wrong from these snippets?
Thanks in advance.
Rob
First, let's confirm you're not doing anything wrong, and the problem has nothing to do with the attached behaviors.
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<Border Background="Yellow">
<StackPanel>
<TextBlock x:Name="theText" Text="Hello" />
<ContentPresenter />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Content" Value="{Binding ElementName=theText, Path=Text}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
This snippet should cause "Hello" to appear twice when I mouse over the button, but it doesn't, and I get the same error as you:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=theText'. BindingExpression:Path=Text; DataItem=null; target element is 'Button' (Name=''); target property is 'Content' (type 'Object')
This is explainable - once the binding is set on the Button, it won't be able to find a control named 'theText', because the Button lives in a different NameScope.
An alternative
Some WPF controls need to do something similar to you - they assume that a specific control exists in the tree that they will interact with. But they don't use properties - they use names.
Start by giving the controls a name - the convention is to use "PART_" prefix:
<Rectangle ... Name="PART_ScrollLeftRect" />
Now put code like this in your callback when IsScrollHover is set:
private static void IsScrollHoverSetCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (Slider) d;
if ((bool)e.NewValue == false)
return;
target.ApplyTemplate();
var leftRectangle = target.Template.FindName("PART_ScrollLeftRect", target);
var rightRectangle = target.Template.FindName("PART_ScrollRightRect", target);
// Do things with the rectangles
}
Note that depending on when the IsScrollHost property is set, the template might not be ready yet. In that case, you might want to subscribe to the Loaded or similar event, and then call ApplyTemplate().
Although it might seem more complicated, it has one nice benefit: the markup will be simpler. A designer using Blend won't have to remember to wire up those complicated triggers, they just have to name the controls correctly.
The use of the PART_ prefix is a WPF convention, and normally used along with the TemplatePart attribute. An example of this is the TextBox. When you override the template of a TextBox, it won't function until you add a control named PART_ContentHost.
Update: I just blogged about template parts here: http://www.paulstovell.com/wpf-part-names

WPF - Redrawing a Context Menu when Items change?

I have a ItemsControl in a ScrollViewer, and when the items exceed the width of the ScrollViewer they are put into a ContextMenu and shown as a DropDown instead. My problem is that when the Context Menu is first loaded, it saves the saves the size of the Menu and does not redraw when more commands get added/removed.
For example, a panel has 3 commands. 1 is visible and 2 are in the Menu. Viewing the menu shows the 2 commands and draws the control, but then if you resize the panel so 2 are visible and only 1 command is in the menu, it doesn't redraw the menu to eliminate that second menu item. Or even worse, if you shrink the panel so that no commands are shown and all 3 are in the Menu, it will only show the top 2.
Here's my code:
<Button Click="DropDownMenu_Click"
ContextMenuOpening="DropDownMenu_ContextMenuOpening">
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding Path=MenuCommands}" Placement="Bottom">
<ContextMenu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="Visibility" Value="{Binding Path=IsVisible, Converter={StaticResource ReverseBooleanToVisibilityConverter}}"/>
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DisplayName}" />
</DataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
Code Behind:
void DropDownMenu_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Button b = sender as Button;
b.ContextMenu.IsOpen = false;
e.Handled = true;
}
private void DropDownMenu_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
ContextMenu cMenu = b.ContextMenu;
if (cMenu != null)
{
cMenu.PlacementTarget = b;
cMenu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
cMenu.IsOpen = true;
}
}
I have tried using InvalidateVisual and passing an empty delegate on Render to try and force a redraw, however neither works. I'm using .Net 4.0.
Is MenuCommands a collection? If it is, is it an ObservableCollection?
If you bind a collection to an ItemsControl, that collection must implement INotifyCollectionChanged interface to let the ItemsControl know that the number of items in the collection has changed, so that the control can "redraw" itself.

How to get a group of toggle buttons to act like radio buttons in WPF?

I have a group of buttons that should act like toggle buttons, but also as radio buttons where only one button can be selected / pressed down at a current time. It also need to have a state where none of the buttons are selected / pressed down.
The behavior will be kind of like Photoshop toolbar, where zero or one of the tools are selected at any time!
Any idea how this can be implemented in WPF?
This is easiest way in my opinion.
<RadioButton Style="{StaticResource {x:Type ToggleButton}}" />
Enjoy!
-- Pricksaw
The easiest way is to style a ListBox to use ToggleButtons for its ItemTemplate
<Style TargetType="{x:Type ListBox}">
<Setter Property="ListBox.ItemTemplate">
<Setter.Value>
<DataTemplate>
<ToggleButton Content="{Binding}"
IsChecked="{Binding IsSelected, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}}"
/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then you can use the SelectionMode property of the ListBox to handle SingleSelect vs MultiSelect.
<RadioButton Content="Point" >
<RadioButton.Template>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Content="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
</ControlTemplate>
</RadioButton.Template>
</RadioButton>
it works for me, enjoy!
you could always use a generic event on the Click of the ToggleButton that sets all ToggleButton.IsChecked in a groupcontrol(Grid, WrapPanel, ...) to false with the help of the VisualTreeHelper; then re-check the sender.
Or something in the likes of that.
private void ToggleButton_Click(object sender, RoutedEventArgs e)
{
int childAmount = VisualTreeHelper.GetChildrenCount((sender as ToggleButton).Parent);
ToggleButton tb;
for (int i = 0; i < childAmount; i++)
{
tb = null;
tb = VisualTreeHelper.GetChild((sender as ToggleButton).Parent, i) as ToggleButton;
if (tb != null)
tb.IsChecked = false;
}
(sender as ToggleButton).IsChecked = true;
}
you can put grid with radiobuttons in it, and create button like template for raduiobuttons. than just programmaticaly remove check if you don't want buttons to be toggled
You can also try System.Windows.Controls.Primitives.ToggleButton
<ToggleButton Name="btnTest" VerticalAlignment="Top">Test</ToggleButton>
Then write code against the IsChecked property to mimick the radiobutton effect
private void btnTest_Checked(object sender, RoutedEventArgs e)
{
btn2.IsChecked = false;
btn3.IsChecked = false;
}
I did this for RibbonToggleButtons, but maybe it's the same for regular ToggleButtons.
I bound the IsChecked for each button to a "mode" enum value using EnumToBooleanConverter from here How to bind RadioButtons to an enum? (Specify the enum value for this button using the ConverterParameter. You should have one enum value for each button)
Then to prevent unchecking a button that's already checked, put this in your code behind for the Click event for each of the RibbonToggleButtons:
private void PreventUncheckRibbonToggleButtonOnClick ( object sender, RoutedEventArgs e ) {
// Prevent unchecking a checked toggle button - so that one always remains checked
// Cancel the click if you hit an already-checked button
var button = (RibbonToggleButton)sender;
if( button.IsChecked != null ) { // Not sure why checked can be null but that's fine, ignore it
bool notChecked = ( ! (bool)button.IsChecked );
if( notChecked ){ // I guess this means the click would uncheck it
button.IsChecked = true;
}
}
}
To help people like Julian and me (two minutes ago...). You can derive from the RadioButton like this.
class RadioToggleButton : RadioButton
{
protected override void OnToggle()
{
if (IsChecked == true) IsChecked = IsThreeState ? (bool?)null : (bool?)false;
else IsChecked = IsChecked.HasValue;
}
}
Then, you can use it like Uday Kiran suggested...
<Window x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Sample"
Title="MainWindow" Height="600" Width="600">
<StackPanel>
<local:RadioToggleButton Content="Button" Style="{StaticResource {x:Type ToggleButton}}" />
</StackPanel>
</Window>
This method allows only one ToggleButton to be Checked at a time, and it also allows UnChecking.
I took a few piece of the answers and added some extra code. Now you can have different groups of toggle buttons which act like one toggle button:
<UserControl.Resources>
<Style x:Key="GroupToggleStyle" TargetType="ToggleButton">
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding GroupName, RelativeSource={RelativeSource Self}}" Value="Group1"/>
<Condition Binding="{Binding BooleanProperty}" Value="true"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="IsChecked" Value="true"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
And the different groups of radio buttons which look like toggle buttons:
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group1" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group2" Style="{StaticResource {x:Type ToggleButton}}">
<Radio Button GroupName="Group3" Style="{StaticResource {x:Type ToggleButton}}">
One simplistic implementation could be where you maintain a flag in your code behind such as:
ToggleButton _CurrentlyCheckedButton;
Then assign a single Checked event handler to all your context ToggleButtons:
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton != null)
_CurrentlyCheckedButton.IsChecked = false;
_CurrentlyCheckedButton = (sender as ToggleButton);
}
And, a single Unchecked event handler:
private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
if (_CurrentlyCheckedButton == (sender as ToggleButton))
_CurrentlyCheckedButton = null;
}
This way you can have the 'zero or one' selection you desire.

Resources