How can I prevent a ToggleButton from being Toggled without setting IsEnabled - wpf

I have a list of ToggleButtons being used as the ItemTemplate in a ListBox similar to this answer using the MultiSelect mode of the Listbox. However I need to make sure at least one item is always selected.
I can get the proper behavior from the ListBox by just adding an item back into the ListBox's SelectedItems collection on the ListBox.SelectionChanged event but my ToggleButton still moves out of its toggled state so I think I need to stop it earlier in the process.
I would like to do it without setting IsEnabled="False" on the last button Selected because I'd prefer to stay with the Enabled visual style without having to redo my button templates. Any ideas?

You can override the OnToggle method to prevent toggling the state, by not calling the base implementation :
public class LockableToggleButton : ToggleButton
{
protected override void OnToggle()
{
if (!LockToggle)
{
base.OnToggle();
}
}
public bool LockToggle
{
get { return (bool)GetValue(LockToggleProperty); }
set { SetValue(LockToggleProperty, value); }
}
// Using a DependencyProperty as the backing store for LockToggle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LockToggleProperty =
DependencyProperty.Register("LockToggle", typeof(bool), typeof(LockableToggleButton), new UIPropertyMetadata(false));
}

Have you tried using RadioButtons instead? It normally can't be deselected without selecting another one. It can also be styled to look like a ToggleButton:
<RadioButton Style="{StaticResource {x:Type ToggleButton}}"/>
Or, if you already have a Style for it, just make it BasedOn="{x:Type ToggleButton}". Note that the Visual Studio Editor shows an error in the first case, but it compiles and works fine.

This is hackey, but if you don't want custom code you could always use the property "IsHitTestVisible", when you don't want them to uncheck it, simply set IsHitTestVisible equal to false. However, they may be able to tab to the control and toggle it using the space bar.

Thomas's answer works fine, but you don't even need the extra dependency property. Your button will update correctly if you have the class inherit from ToggleButton so you can override the OnToggle method, and you change the IsChecked bound property on the ViewModel.
Xaml:
<myControls:OneWayFromSourceToTargetToggle x:Name="MyCustomToggleButton"
Command="{Binding Path=ToggleDoStuffCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=Self}}"
IsChecked="{Binding Path=ToggleIsCheckedConditionVar,
Mode=OneWay}"
/>
Added ToggleButton Class:
public class OneWayFromSourceToTargetToggle : ToggleButton
{
/// <summary>
/// Overrides the OnToggle method, so it does not set the IsChecked Property automatically
/// </summary>
protected override void OnToggle()
{
// do nothing
}
}
Then in the ViewModel just set bool ToggleIsCheckedCondition to true or false. This is a nice way to do it because you are following good MVVM practices.
ViewModel:
public bool ToggleIsCheckedCondition
{
get { return _toggleIsCheckedCondition; }
set
{
if (_toggleIsCheckedCondition != value)
{
_toggleIsCheckedCondition = value;
NotifyPropertyChanged("ToggleIsCheckedCondition");
}
}
}
public ICommand ToggleDoStuffCommand
{
get {
return _toggleDoStuffCommand ??
(_toggleDoStuffCommand = new RelayCommand(ExecuteToggleDoStuffCommand));
}
}
private void ExecuteToggleDoStuffCommand(object param)
{
var btn = param as ToggleButton;
if (btn?.IsChecked == null)
{
return;
}
// has not been updated yet at this point
ToggleIsCheckedCondition = btn.IsChecked == false;
// do stuff
}
}

Adding a little bit to #Joachim-Mairböck's great answer in case you want to do the same programmatically:
new RadioButton {
...
GroupName = "myButtonGroup"
Style = Application.Current.TryFindResource(typeof(ToggleButton)) as Style
...
}

Related

Fluent Ribbon SelectedTabItem or SelectedTabIndex

I'm using Fluent Ribbon in a MVVM design pattern, with a View/ViewModel. My goal is to change the body according to which tab is selected. I could use either SelectedTabItem or SelectedTabIndex and lookup the corresponding view. However, neither fire when the tab selection changes. In the View I have...
<Fluent:Ribbon Grid.Row="0" SelectedTabItem="{Binding SelectedRibbonTab}" SelectedTabIndex="{Binding SelectedRibbonTabIndex}">
</Fluent:Ribbon>
<ContentControl Grid.Row="1" Content="{Binding RibbonTabContent}"/>
In the ViewModel I have...
// Used both Item and Index for troubleshoothing, but need only one or the other for implementation
private IDictionary<string, FrameworkElement> RibbonTabViews;
private List<FrameworkElement> RibbonTabViewsList;
public RibbonTabItem SelectedRibbonTab
{
get
{
return selectedRibbonTab;
}
set
{
Update(() => SelectedRibbonTab, ref selectedRibbonTab, value, false);
UpdateContentControl();
}
}
public int SelectedRibbonTabIndex
{
get
{
return selectedRibbonTabIndex;
}
set
{
Update(() => SelectedRibbonTabIndex, ref selectedRibbonTabIndex, value, false);
UpdateContentControl(value);
}
}
public FrameworkElement RibbonTabContent
{
get { return ribbonTabContent; }
set { Update(() => RibbonTabContent, ref ribbonTabContent, value, false); }
}
protected void UpdateContentControl()
{
RibbonTabContent = RibbonTabViews[SelectedRibbonTab.Header.ToString()];
}
protected void UpdateContentControl(int index)
{
RibbonTabContent = RibbonTabViewsList[index];
}
I know I don't need both bindings but for the sake of troubleshoot I have both Item and Index. I would think in the ViewModel SelectedRibbonTab and SelectedRibbonTabIndex would be called each time the tab changed. Unfortunately, that doesn't appear to be the case. I have breakpoints at the setters and getters for each and neither are hit when changing tabs. What am I missing? I've been using this approach for years with Microsoft Ribbon but for some reason these don't fire in Fluent Ribbon. Any help would be great, thank you.
You have to set the binding mode to TwoWay to get updated values in your ViewModel.

Can't bind to IsExpanded on Expander

Blacklight is an older set of WPF controls and styles. The code can be found here. It contains a control called AnimatedExpander which isn't really an expander, rather it just implements HeaderedContentControl and adds an IsExpandedProperty dprop:
public static readonly DependencyProperty IsExpandedProperty =
DependencyProperty.Register("IsExpanded", typeof(bool), typeof(AnimatedExpander), new PropertyMetadata(true));
public bool IsExpanded
{
get
{
if (this.expandToggleButton != null)
{
return this.expandToggleButton.IsChecked.Value;
}
return (bool)GetValue(IsExpandedProperty);
}
set
{
SetValue(IsExpandedProperty, value);
}
}
I need to bind to IsExpanded so that I can persist whether expanders are expanded. I'm pretty sure I have the binding setup correctly, and that there is a problem with this custom dependency property. If I open the view in Snoop, and set the IsExpanded=True on the expander, the binding works. However, just clicking the expandToggleButton on the control only expands the control, it doesn't hit my binding.
<controls:AnimatedExpander IsExpanded="{Binding SGGExpanderExpanded}" />
private bool _sGGExpanderExpanded;
public bool SGGExpanderExpanded
{
get { return _sGGExpanderExpanded; }
set
{
if (_sGGExpanderExpanded != value)
{
_sGGExpanderExpanded = value;
OnPropertyChanged("SGGExpanderExpanded");
}
}
}
How can I bind to a value that changes when the user clicks the toggle button that is wired to expand the control?
A bad solution:
I was able to make this work by attaching an event to the ToggleButton click and looking at the "sender" Content and IsChecked values to update my viewmodel.

Bind a views DialogResult to a ViewModels property?

I am relatively new to WPF, XAML and Data-bindings. I have a view (Window) and a view-model.
I have tried to implement the MVVM pattern which means neither the view nor the view-model hold a reference to each other. All data exchange happens via data-bindings.
So far so good but now I have run into a problem I can't find a solution for.
On my view I have a button Start which is bound to a command.
<Button Command="{Binding NextCommand}" Content="Next">
NextCommand is of type ActionCommand : ICommand
In my case NextCommand simply calls a private method within the view-model.
The problem I can not find a solution so far is the following:
How to close the window at the end of the view-models NextCommandAction method?
private void NextCommandAction(object o)
{
...
...
// close the window
}
Since I do not have a reference to the view I can not just set DialogResult = true;
The only working solution I have found so far is to add a hidden radio-button to the view and bind it's value to a property CloseView and create a method CloseView within the xaml.cs file which is bound to the Checked event of the hidden radio-button. Within that method I set DialogResult = true;
Although this works I feel like there has to be a better solution than adding hidden elements to your view!
You can pass the window reference as CommandParameter to the Close command and do whatever required on the window.
<Button Content="Close" Command="{Binding Path=CloseCommand}"
CommandParameter="{Binding ElementName=Window}"/>
private void CloseCommand(object sender)
{
Window wnd = sender as Window;
wnd.Close();
}
CommandParameter="{Binding ElementName=Window}" assumes that you have an element in your XAML named "Window". e.g, your Window tag would need Name="Window"
This question was one of the first things that came up when I googled to check if DialogResult is a dependency property (it isn't :-) )
Add a dependency property to your Window:
public static readonly DependencyProperty InteractionResultProperty =
DependencyProperty.Register(
nameof(InteractionResult),
typeof(Boolean?),
typeof(MyWpfWindow1),
new FrameworkPropertyMetadata(default(Boolean?),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnInteractionResultChanged));
public Boolean? InteractionResult
{
get => (Boolean?) GetValue(InteractionResultProperty);
set => SetValue(InteractionResultProperty, value);
}
private static void OnInteractionResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWpfWindow1) d).DialogResult = e.NewValue as Boolean?;
}
I named my property InteractionResult though a good name would have also worked.
In the xaml right after the
you can bind it with a style
<Window.Style>
<Style TargetType="{x:Type z:MyWpfWindow1}">
<Setter Property="InteractionResult"
Value="{Binding UpdateResult}" />
</Style>
</Window.Style>
UpdateResult is the property in my viewmodel.
private Boolean? _updateResult;
public Boolean? UpdateResult
{
get => _updateResult;
set => SetValue(ref _updateResult, value);
}
The SetValue method is the usual notify property
protected virtual Boolean SetValue<T>(ref T field, T value,
[CallerMemberName]String propertyName = null)
{
if (Equals(field, value))
return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
and the property gets set in the usual way
<Button Content="Cancel"
Command="{Binding CancelCommand}" />
ICommand CancelCommand { get; }
private void OnCancel()
{
UpdateResult = false;
}
Disclaimer: works on my computer.
Inspired by Chandrashekhar Joshi's answer
(but not using the elements's name):
Define CommandParameter in Button:
<Button
  Command="{Binding CloseCommand}"
  CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
  Content="Close" />
Define Command (and Implementation):
CloseCommand = new DelegateCommand<Window>((w) => w.DialogResult = true);

Changing VisualState of a control by binding it with some property of ViewModel

Considering the code below:
xmlns:interactivity="clr-namespace:Microsoft.Expression.Interactivity;assembly=Microsoft.Expression.Interactivity"
...
<ToggleButton IsChecked="{Binding Path=IsGlobalControllerAttached}" Command="{Binding Path=AttachDetachGlobalControllerAction}" ToolTip="{Binding Path=GlobalControllerToolTip}" Visibility="{Binding Path=CanApplyDateFilter, Converter={StaticResource bool2VisibilityConverter}}" Style="{StaticResource toolBarToggleButton}">
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsGlobalControllerCreated}" Value="true" TrueState="Normal" FalseState="Disabled" />
</i:Interaction.Behaviors>
<Image Source="../../Common/Images/pin.png"/>
</ToggleButton>
I am trying to set VisualState of Toggle Button by binding it to some property in ViewModel.
Here, I am not able to find the Microsoft.Expression.Interactivity.dll in the "Add Reference" list. I am using VS 2010.
What am i missing? Do i need to install Expression blend to get this dll?
Also,
Is there any other way to get the job done? ( Changing VisualState of a control by biding it with some property of ViewModel).
Thanks for your interest.
We use Attached Properties to manage custom state changes on elements. These are then just bound to the view model.
e.g. for a "split screen" setting we do the following.
Create a DependancyProperty in a class called SplitScreen, with a property called Mode:
public class SplitScreen
{
public static readonly DependencyProperty ModeProperty =
DependencyProperty.Register("Mode",
typeof(SplitScreenMode),
typeof(UserControl),
new PropertyMetadata(SplitScreenMode.None,
new PropertyChangedCallback(OnScreenModeChanged)));
public static void SetMode(DependencyObject obj, SplitScreenMode value)
{
obj.SetValue(ModeProperty, value);
}
public static SplitScreenMode GetMode(Control obj)
{
return (SplitScreenMode)obj.GetValue(ModeProperty);
}
static void OnScreenModeChanged(object sender, DependencyPropertyChangedEventArgs args)
{
var control = sender as UserControl;
if (control != null)
{
if (control.Parent == null)
{
control.Loaded += (s, e) =>
{
ApplyCurrentState(control);
};
}
else
{
ApplyCurrentState(control);
}
}
}
[snip]
}
You might note our little trick to late-update the value when Attached Property is initially set (there is often no parent element until the page is fully loaded).
In the Xaml file attach the property to the required element like this:
lib:SplitScreen.Mode="{Binding SplitScreenMode}"
The key is to catch dependency property changes and get that to change the visual state of the attached element (this is the snipped part of the SplitScreen.cs file):
static public void ApplyCurrentState(Control control)
{
string targetState;
switch (GetMode(control))
{
case SplitScreenMode.Single:
targetState = SplitScreenModeName.Single;
break;
case SplitScreenMode.Dual:
targetState = SplitScreenModeName.Dual;
break;
default:
targetState = SplitScreenModeName.None;
break;
}
VisualStateManager.GoToState(control, targetState, true);
}
The alternative is to install the Expression Blend SDK
You do not need Expression Blend to make use of the SDK and all the cool extras. It is a lot less work for simple items (we just needed some custom behaviour it did not support).

WPF Inner Property Binding not updating

I have an INotifyProperty Screen item that I have bound to a wpf control.
Ok... I Simplified everything and am posting more code. I have a MainViewModel with the selected screen property.
public Screen SelectedScreen
{
get { return this.selectedScreen; }
set
{
this.selectedScreen = value;
this.OnPropertyChanged("SelectedScreen");
}
}
I have a textbox that is bound to this property:
<TextBlock Text="{Binding Path=SelectedScreen.ScreenNumber}" />
This all works initially. I have created another control that is changing the selected screen with the following code.
public Screen SelectedScreen
{
get { return (Screen)GetValue(SelectedScreenProperty); }
set
{
this.SetValue(SelectedScreenProperty, value);
for (int x = 0; x < this.Screens.Count; ++x)
this.Screens[x].IsSelected = false;
value.IsSelected = true;
}
}
public ObservableCollection<Screen> Screens
{
get { return (ObservableCollection<Screen>)GetValue(ScreensProperty); }
set { this.SetValue(ScreensProperty, value); }
}
public static readonly DependencyProperty SelectedScreenProperty =
DependencyProperty.Register("SelectedScreen",
typeof(Screen),
typeof(ScreenSelection));
public static readonly DependencyProperty ScreensProperty =
DependencyProperty.Register("Screens",
typeof(ObservableCollection<Screen>),
typeof(ScreenSelection),
new UIPropertyMetadata(new ObservableCollection<Screen>()));
This screen selection control is working. When I change screens and put a breakpoint on the set property of SelectedScreen it is called which then calls the SelectedScreen property of the MainViewModel. So the event is firing, but the textbox isn't updated even though it binds correctly the first time.
Does the class which contains the SelectedScreen property implement INotifyPropertyChanged? When the SelectedScreen property changes, the containing class should raise the PropertyChanged event, and typically, WPF should update the Binding.
Thank you gehho for looking at this. I figured it out and there is no way you had enough information to be able too. I was inheriting from ViewModelBase in the MainViewModel that was inheriting from ObservableObject where I implemented INotifyPropertyChanged. The problem is that I implemented the methods for INotifyPropertyChanged in both classes and WPF was listening to the wrong one. Very obscure. Very annoying. Very lasjkdf;ashdoh

Resources