WPF MVVM show hidden window and set focus - c#

I would like to show/activate a hidden window and set the (keyboard-)focus to a textbox programmatically (without violating mvvm if possible).
So if the the user presses a hotkey (already implemented) following should happen:
Window gets shown + activated
Textbox gets focused + keyboard focus (so the user can type some text)
Pressing the hotkey again should hide the window.
I tried binding Activated and Visibility to a boolean value, but I ran into some problems with TwoWay-Binding. Thank you for any ideas on how to solve this problem.
Edit:
MainWindow.xml:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVisConverter" />
</Window.Resources>
<i:Interaction.Behaviors>
<bh:ActivateBehavior Activated="{Binding Visible, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
<Window.Visibility>
<Binding Path="Visible" Mode="TwoWay" Converter="{StaticResource BoolToVisConverter}"/>
</Window.Visibility>
<TextBox Grid.Column="1" x:Name="SearchBar" bh:FocusExtension.IsFocused="{Binding Visible, Mode=TwoWay}"/>
ActivateBehavior.cs:
used from this https://stackoverflow.com/a/12254217/13215602
public class ActivateBehavior : Behavior<Window> {
Boolean isActivated;
public static readonly DependencyProperty ActivatedProperty =
DependencyProperty.Register(
"Activated",
typeof(Boolean),
typeof(ActivateBehavior),
new PropertyMetadata(OnActivatedChanged)
);
public Boolean Activated {
get { return (Boolean) GetValue(ActivatedProperty); }
set { SetValue(ActivatedProperty, value); }
}
static void OnActivatedChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var behavior = (ActivateBehavior) dependencyObject;
if (!behavior.Activated || behavior.isActivated)
return;
// The Activated property is set to true but the Activated event (tracked by the
// isActivated field) hasn't been fired. Go ahead and activate the window.
if (behavior.AssociatedObject.WindowState == WindowState.Minimized)
behavior.AssociatedObject.WindowState = WindowState.Normal;
behavior.AssociatedObject.Activate();
}
protected override void OnAttached() {
AssociatedObject.Activated += OnActivated;
AssociatedObject.Deactivated += OnDeactivated;
}
protected override void OnDetaching() {
AssociatedObject.Activated -= OnActivated;
AssociatedObject.Deactivated -= OnDeactivated;
}
void OnActivated(Object sender, EventArgs eventArgs) {
this.isActivated = true;
Activated = true;
}
void OnDeactivated(Object sender, EventArgs eventArgs) {
this.isActivated = false;
Activated = false;
}
}
FocusExtension.cs: used from this https://stackoverflow.com/a/1356781/13215602
public static class FocusExtension
{
public static bool GetIsFocuses(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty =
DependencyProperty.RegisterAttached("IsFocused", typeof(bool), typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged));
private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement) d;
if ((bool) e.NewValue)
{
Keyboard.Focus(uie);
uie.Focus();
}
}
}
MainViewModel.cs:
private bool mVisible;
//Binding to this variable
public bool Visible
{
get { return mVisible; }
set
{
if (mVisible == value)
return;
mVisible = value;
RaisePropertyChanged(nameof(Visible));
}
}

Related

WPF PropertyMetadata does not work properly

I have a ToggleSwitch UserControl. ToggleSwitch has DependencyProperty called IsChecked and this shows check status of UserControl
ToggleSwitch.xaml.cs :
public bool IsChecked {
get { return ( bool ) GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(ToggleSwitch),
new PropertyMetadata(false, IsCheckedPropertyOnChanged));
private static void IsCheckedPropertyOnChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if ( obj is ToggleSwitch ) {
var res = ( ( ToggleSwitch ) obj );
res.ChangeStatus();
}
}
At the same time IsChecked status can change when 'GridMouseLeftButtonDown' event occurs (So the user click the ToggleSwitch):
private void GridOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
IsChecked = !IsChecked;
}
The appearance changes with the ChangeStatus function.
Below code shows how I bind the IsChecked in MainWindow.xaml
<local:ToggleSwitch IsChecked="{Binding SelectedPerson.IsActive}"/>
SelectedPersonis a DependencyProperty in MainWindow.xaml.cs.
The IsChecked property changes successfully every time I create a new instance of SelectedPerson.
But once GridOnMouseLeftButtonDown (ToggleSwitch ClickEvent) fires, the IsCheckedPropertyOnChanged no longer invokes.
Small project example to show the problem:
ToggleSwitch.xaml.cs:
public partial class ToggleSwitch : UserControl {
public bool IsChecked {
get { return ( bool ) GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(ToggleSwitch),
new PropertyMetadata(false, IsCheckedPropertyOnChanged));
private static void IsCheckedPropertyOnChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) {
if ( obj is ToggleSwitch ) {
var res = ( ( ToggleSwitch ) obj );
res.ChangeStatus();
}
}
public ToggleSwitch() {
InitializeComponent();
}
private void ChangeStatus() {
if ( IsChecked ) {
MainContainer.Background = Brushes.Green;
} else {
MainContainer.Background = Brushes.Red;
}
}
private void GridOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
IsChecked = !IsChecked;
}
}
ToggleSwitch.xaml:
<Grid MouseLeftButtonDown="GridOnMouseLeftButtonDown" Width="150" Height="150">
<Border x:Name="MainContainer" Width="150" Height="150" BorderThickness="1" BorderBrush="Black" Background="White"/>
</Grid>
MainWindow.xaml.cs:
public class Person {
public bool IsActive { get; set; }
}
public partial class MainWindow : Window {
public Person SelectedPerson {
get { return ( Person ) GetValue(SelectedPersonProperty); }
set { SetValue(SelectedPersonProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedPerson. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPersonProperty =
DependencyProperty.Register("SelectedPerson", typeof(Person), typeof(MainWindow));
public MainWindow() {
InitializeComponent();
this.DataContext = this;
SelectedPerson = new Person {
IsActive = true
};
}
private void ButtonOnClicked(object sender, RoutedEventArgs e) {
var change = !SelectedPerson.IsActive;
SelectedPerson = new Person {
IsActive = change
};
}
}
MainWindow.xaml:
<StackPanel Orientation="Vertical">
<local:ToggleSwitch IsChecked="{Binding SelectedPerson.IsActive}" />
<Button Content="ChangeItemSource" Click="ButtonOnClicked" Width="150" Margin="50" />
</StackPanel>

UWP CommandBar Binding

is it possible to bind an UWP CommandBar to something like a ObservableCollection or so?
What i want to achieve ist to bind my CommandBar of my NavigationView to an Object of a specific Page so that the AppBarButton change dynamicaly depending on the current Page
What i tryed:
MainPage.xaml
<NavigationView.HeaderTemplate>
<DataTemplate>
<Grid>
<CommandBar Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Top"
DefaultLabelPosition="Right"
Background="{ThemeResource SystemControlBackgroundAltHighBrush}" Content="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}">
</CommandBar>
</Grid>
</DataTemplate>
</NavigationView.HeaderTemplate>
SomePage.xaml.cs
public ObservableCollection<AppBarButton> AppBarButtonList = new ObservableCollection<AppBarButton> {
new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
};
But the CommandBar shows nothing.
Thanks.
My original solution was using the PrimaryCommands property to bind the commands, but it turns out this property is read-only.
My solution to the problem will be using behaviors.
First add a reference to Microsoft.Xaml.Behaviors.Uwp.Managed from NuGet.
Then add the following behavior to your project:
public class BindableCommandBarBehavior : Behavior<CommandBar>
{
public ObservableCollection<AppBarButton> PrimaryCommands
{
get { return (ObservableCollection<AppBarButton>)GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(ObservableCollection<AppBarButton>), typeof(BindableCommandBarBehavior), new PropertyMetadata(default(ObservableCollection<AppBarButton>), UpdateCommands));
private static void UpdateCommands(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
var oldList = dependencyPropertyChangedEventArgs.OldValue as ObservableCollection<AppBarButton>;
if (dependencyPropertyChangedEventArgs.OldValue != null)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
var newList = dependencyPropertyChangedEventArgs.NewValue as ObservableCollection<AppBarButton>;
if (dependencyPropertyChangedEventArgs.NewValue != null)
{
newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
}
behavior.UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private void UpdatePrimaryCommands()
{
if (PrimaryCommands != null)
{
AssociatedObject.PrimaryCommands.Clear();
foreach (var command in PrimaryCommands)
{
AssociatedObject.PrimaryCommands.Add(command);
}
}
}
protected override void OnDetaching()
{
base.OnDetaching();
if (PrimaryCommands != null)
{
PrimaryCommands.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
}
}
This behavior essentially creates a fake PrimaryCommands property that is bindable and also observes collection changed events. Whenever a change occurs, the commands are rebuilt.
Finally, the problem in your code is that your AppBarButtonList is just a field, not a property. Change it like this:
public ObservableCollection<AppBarButton> AppBarButtonList { get; } = new ObservableCollection<AppBarButton> {
new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Bla" },
new AppBarButton{Icon=new SymbolIcon(Symbol.Add),Label="Add"}
};
Notice the {get ;} which was added before the assignment operator.
Now you can use the behavior in XAML like this:
<CommandBar>
<interactivity:Interaction.Behaviors>
<local:BindableCommandBarBehavior PrimaryCommands="{Binding Path=Content.AppBarButtonList, ElementName=rootFrame}" />
</interactivity:Interaction.Behaviors>
</CommandBar>
This is by no means a perfect solution and could be improved upon to allow different collection types binding and more, but it should cover your scenario. An alternative solution would be to implement a custom version of command bar, with new additional dependency property directly on the type, but I used behavior to make it clearer for the user that this is an "added" functionality, not a built-in one.
I found this answer very helpful. I did some more adjustments, like using a DataTemplateSelector to remove UI references like "AppBarButton" from the bindable data source.
public class BindableCommandBarBehavior : Behavior<CommandBar>
{
public static readonly DependencyProperty PrimaryCommandsProperty = DependencyProperty.Register(
"PrimaryCommands", typeof(object), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, UpdateCommands));
public static readonly DependencyProperty ItemTemplateSelectorProperty = DependencyProperty.Register(
"ItemTemplateSelector", typeof(DataTemplateSelector), typeof(BindableCommandBarBehavior),
new PropertyMetadata(null, null));
public DataTemplateSelector ItemTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ItemTemplateSelectorProperty); }
set { SetValue(ItemTemplateSelectorProperty, value); }
}
public object PrimaryCommands
{
get { return GetValue(PrimaryCommandsProperty); }
set { SetValue(PrimaryCommandsProperty, value); }
}
protected override void OnDetaching()
{
base.OnDetaching();
if (PrimaryCommands is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged -= PrimaryCommandsCollectionChanged;
}
}
private void UpdatePrimaryCommands()
{
if (AssociatedObject == null)
return;
if (PrimaryCommands == null)
return;
AssociatedObject.PrimaryCommands.Clear();
if (!(PrimaryCommands is IEnumerable enumerable))
{
AssociatedObject.PrimaryCommands.Clear();
return;
}
foreach (var command in enumerable)
{
var template = ItemTemplateSelector.SelectTemplate(command, AssociatedObject);
if (!(template?.LoadContent() is FrameworkElement dependencyObject))
continue;
dependencyObject.DataContext = command;
if (dependencyObject is ICommandBarElement icommandBarElement)
AssociatedObject.PrimaryCommands.Add(icommandBarElement);
}
}
protected override void OnAttached()
{
base.OnAttached();
UpdatePrimaryCommands();
}
private void PrimaryCommandsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdatePrimaryCommands();
}
private static void UpdateCommands(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;
if (dependencyPropertyChangedEventArgs.OldValue is INotifyCollectionChanged oldList)
{
oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
}
if (dependencyPropertyChangedEventArgs.NewValue is INotifyCollectionChanged newList)
{
newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
}
behavior.UpdatePrimaryCommands();
}
}
The DataTemplateSelector:
public class CommandBarMenuItemTemplateSelector : DataTemplateSelector
{
public DataTemplate CbMenuItemTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item is ContextAction)
{
return CbMenuItemTemplate;
}
return base.SelectTemplateCore(item, container);
}
}
Xaml for templates:
<DataTemplate x:Key="CbMenuItemTemplate">
<AppBarButton
Command="{Binding Command}"
Icon="Add"
Label="{Binding Text}" />
</DataTemplate>
<viewLogic:CommandBarMenuItemTemplateSelector x:Key="CommandBarMenuItemTemplateSelector"
CbMenuItemTemplate="{StaticResource CbMenuItemTemplate}" />
Usage:
<CommandBar>
<interactivity:Interaction.Behaviors>
<viewLogic:BindableCommandBarBehavior ItemTemplateSelector="{StaticResource CommandBarMenuItemTemplateSelector}" PrimaryCommands="{Binding ContextActions}" />
</interactivity:Interaction.Behaviors>
</CommandBar>
Where ContextActions is a ObservableCollection of my class ContextAction.

Dependency property not working on a interaction behavior

I am trying to add a dependency property IsActive to a RubberBandBehavior I found on CodeProject, so that I can activate and deactivate it from my ViewModel. The code below does not give any compile error and runs, but the value does not seem to be set correctly, when I check the line with the comment \\ this line is always 'false'.
The modified class RubberBandBehavior:
public class RubberBandBehavior : Behavior<ListBox>
{
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register("IsActive", typeof(bool), typeof(RubberBandBehavior),
new PropertyMetadata(IsActiveProperty_Changed));
private static void IsActiveProperty_Changed(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
// This gets called _after_ OnAttached!
}
public bool IsActive
{
get { return (bool)GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
protected override void OnAttached()
{
AssociatedObject.Loaded += new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnAttached();
}
void AssociatedObject_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
bool a = IsActive; // this line is always 'false'
RubberBandAdorner band = new RubberBandAdorner(AssociatedObject);
AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AssociatedObject);
adornerLayer.Add(band);
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= new System.Windows.RoutedEventHandler(AssociatedObject_Loaded);
base.OnDetaching();
}
}
In my XAML I have this:
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior IsActive="True"/>
</i:Interaction.Behaviors>
My plan is then to bind to my ViewModel like this, but first I need to get the above sorted out:
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior IsActive="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.IsEditable}"/>
</i:Interaction.Behaviors>
You wont get this value in the AssociatedObject.Loaded event. You need to use the IsActiveProperty_Changed handler in your code example to get the correct value of this property

Move-able Expander

I am trying to create a user move-able expander for my WPF project. Ideally, if the user clicks the expander button, s/he should be able to expand. If one clicks and drags the text box that say "Advanced Options" I want that person to be able to move the expander around. My code doesn't work and just expands the expander. I tried associating the click on the header with something different than expanding, but that doesn't seem to work. The xaml and C# code is below. Help?
<Expander HorizontalAlignment="Left" Margin="89,372,0,0" VerticalAlignment="Top" Height="453" Width="909" IsExpanded="True" x:Name="grid_expander">
<Expander.Header>
<TextBlock HorizontalAlignment="Left" Height="22" Margin="5,0,0,0" TextWrapping="Wrap" Width="154" Foreground="White" FontSize="18" MouseDown="grid_expander_MouseDown" MouseUp="grid_expander_MouseUp" MouseMove="grid_expander_MouseMove">
Advanced Options
</TextBlock>
</Expander.Header>
<Expander.RenderTransform>
<TranslateTransform x:Name="expander_xy"/>
</Expander.RenderTransform>
</Expander>
private void grid_expander_MouseDown(object sender, MouseButtonEventArgs e)
{
//e.Handled = true;
m_start = e.GetPosition(window);
m_startOffset = new Vector(expander_xy.X, expander_xy.Y);
grid_expander.CaptureMouse();
}
private void grid_expander_MouseMove(object sender, MouseEventArgs e)
{
if (grid_expander.IsMouseCaptured)
{
Vector offset = Point.Subtract(e.GetPosition(window), m_start);
expander_xy.X = m_startOffset.X + offset.X;
expander_xy.Y = m_startOffset.Y + offset.Y;
}
}
private void grid_expander_MouseUp(object sender, MouseButtonEventArgs e)
{
grid_expander.ReleaseMouseCapture();
}
I have did the same using an Image Control instead of Expander Control, I believe you can replace the Image Control and place your Expander Control I will share my code. Try doing with Expander Control Let me know if it helps.
XAML
<Grid
Background="Black"
mousebehav:MouseBehaviour.MouseLeftButtonUpCommand="{Binding MLBUCommand}">
<Canvas >
<Image RenderTransformOrigin="0.5,0.5"
Source="{Binding ImageSource}"
Stretch="None" SnapsToDevicePixels="False"
mousebehav:MouseBehaviour.MouseLeftButtonDownCommand="{Binding MDCommand}"
mousebehav:MouseBehaviour.MouseMoveCommand="{Binding MMCommand}">
<Image.RenderTransform>
<TranslateTransform
X="{Binding MouseX}"
Y="{Binding MouseY}" />
</Image.RenderTransform>
<Image.LayoutTransform>
<TransformGroup>
<ScaleTransform
ScaleX="{Binding ScaleX}"
ScaleY="{Binding ScaleY}"/>
<RotateTransform
Angle="{Binding RotateAngle}"/>
</TransformGroup>
</Image.LayoutTransform>
</Image>
</Canvas>
</Grid>
View Model
private double _ScaleX;
public double ScaleX
{
get { return _ScaleX; }
set { _ScaleX = value; NotifyPropertyChanged(); }
}
private double _ScaleY;
public double ScaleY
{
get { return _ScaleY; }
set { _ScaleY = value; NotifyPropertyChanged(); }
}
private double _RotateAngle;
public double RotateAngle {
get { return _RotateAngle; }
set { _RotateAngle = value; NotifyPropertyChanged(); }
}
private double _MouseX;
public double MouseX
{
get { return _MouseX; }
set { _MouseX = value; NotifyPropertyChanged(); }
}
private double _MouseY;
public double MouseY
{
get { return _MouseY; }
set { _MouseY = value; NotifyPropertyChanged(); }
}
public bool IsMouseCaptured { get; set; }
private RelayCommand _mouseDownCommand;
public RelayCommand MDCommand
{
get
{
if (_mouseDownCommand == null) return _mouseDownCommand = new RelayCommand(param => ExecuteMouseDown((MouseEventArgs)param));
return _mouseDownCommand;
}
set { _mouseDownCommand = value; }
}
System.Windows.Point start;
System.Windows.Point origin;
private void ExecuteMouseDown(MouseEventArgs e)
{
var image = (System.Windows.Controls.Image)e.OriginalSource;
var border = (IInputElement)image.Parent;
start = e.GetPosition(border);
origin = new System.Windows.Point(MouseX, MouseY);
IsMouseCaptured = true;
}
private RelayCommand _mouseMoveCommand;
public RelayCommand MMCommand
{
get
{
if (_mouseMoveCommand == null) return _mouseMoveCommand = new RelayCommand(param => ExecuteMouseMove((MouseEventArgs)param));
return _mouseMoveCommand;
}
set { _mouseMoveCommand = value; }
}
private void ExecuteMouseMove(MouseEventArgs e)
{
if(IsMouseCaptured)
{
var image = (System.Windows.Controls.Image)e.OriginalSource;
var border = (IInputElement)image.Parent;
Vector v = start - e.GetPosition(border);
MouseX = origin.X - v.X;
MouseY = origin.Y - v.Y;
}
}
private RelayCommand _MLBUCommand;
public RelayCommand MLBUCommand
{
get
{
if (_MLBUCommand == null) return _MLBUCommand = new RelayCommand(param => Execute_MLBU((MouseEventArgs)param));
return _MLBUCommand;
}
set { _MLBUCommand = value; }
}
private void Execute_MLBU(MouseEventArgs param)
{
IsMouseCaptured = false;
}
Mouse Behaviors as Attached properties
public class MouseBehaviour
{
#region MouseUp
public static readonly DependencyProperty MouseUpCommandProperty =
DependencyProperty.RegisterAttached("MouseUpCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseUpCommandChanged)));
private static void MouseUpCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseUp += element_MouseUp;
}
static void element_MouseUp(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseUpCommand(element);
command.Execute(e);
}
public static void SetMouseUpCommand(UIElement element, ICommand value)
{
element.SetValue(MouseUpCommandProperty, value);
}
public static ICommand GetMouseUpCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseUpCommandProperty);
}
#endregion
#region MouseDown
public static readonly DependencyProperty MouseDownCommandProperty =
DependencyProperty.RegisterAttached("MouseDownCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseDownCommandChanged)));
private static void MouseDownCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseDown += element_MouseDown;
}
static void element_MouseDown(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseDownCommand(element);
command.Execute(e);
}
public static void SetMouseDownCommand(UIElement element, ICommand value)
{
element.SetValue(MouseDownCommandProperty, value);
}
public static ICommand GetMouseDownCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseDownCommandProperty);
}
#endregion
#region MouseMove
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseMoveCommandChanged)));
private static void MouseMoveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseMove += new MouseEventHandler(element_MouseMove);
}
static void element_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseMoveCommand(element);
command.Execute(e);
}
public static void SetMouseMoveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseMoveCommandProperty, value);
}
public static ICommand GetMouseMoveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseMoveCommandProperty);
}
#endregion
}

Return null in boolean to checkbox state converter in XAML

I have a TaskStatus to Boolean converter that implements the IValueConverter interface in XAML for Windows Store Apps (universal apps).
I have three task states and I enabled the indeterminate state in a checkbox using IsThreeState="true".
Now although the IsChecked property seems to be a Boolean?, the converter always gets System.Boolean as target type. Whatever I return (null for example) always converts to false and therefore I can't get the third state in my checkbox.
Is there a way to either specify the TargetType in my converter or return a null so that IsChecked gets null as input and therefore shows the third state?
Here is the converter:
public class TaskStatusToCheckBoxStateConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var taskStatus = (TaskStatus) value;
switch (taskStatus)
{
case TaskStatus.Open:
return false;
case TaskStatus.InProgress:
return null;
case TaskStatus.Done:
return true;
default:
throw new ArgumentOutOfRangeException();
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
var checkBoxState = (Boolean?) value;
if (checkBoxState == null)
return TaskStatus.InProgress;
if (checkBoxState.Value)
return TaskStatus.Done;
return TaskStatus.Open;
}
}
XAML-Code for the Checkbox
<CheckBox x:Name="CheckBoxTaskState"
IsThreeState="True"
IsChecked="{Binding Status,
Converter={StaticResource TaskStatusToCheckBoxStateConverter},
Mode=TwoWay}">
</CheckBox>
According to [this][1]: Binding to nullable types in WinRT isn't supported at this time. How's that for an undocumented rule? Now you know.
Start with this
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
private void NullButton_Click(object sender, RoutedEventArgs e)
{ this.State = null; }
private void FalseButton_Click(object sender, RoutedEventArgs e)
{ this.State = false; }
private void TrueButton_Click(object sender, RoutedEventArgs e)
{ this.State = true; }
bool? _State = null;
public bool? State { get { return _State; } set { SetProperty(ref _State, value); } }
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
void SetProperty<T>(ref T storage, T value, [System.Runtime.CompilerServices.CallerMemberName] String propertyName = null)
{
if (!object.Equals(storage, value))
{
storage = value;
if (PropertyChanged != null)
PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
}
}
}
And this
<Grid x:Name="grid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<Button Click="TrueButton_Click" Content="True" />
<Button Click="FalseButton_Click" Content="False" />
<Button Click="NullButton_Click" Content="Null" />
</StackPanel>
<TextBlock Text="{Binding State}" />
<CheckBox x:Name="checkBox"
Content="Hello three-state"
IsThreeState="True"
IsChecked="{Binding State, Mode=TwoWay}" />
</StackPanel>
</Grid>
You can verify this error in your Output window. It reads something like this:
Error: Converter failed to convert value of type 'Boolean' to type 'IReference1<Boolean>'; BindingExpression: Path='State' DataItem='App4.MainPage'; target element is 'Windows.UI.Xaml.Controls.CheckBox' (Name='checkBox'); target property is 'IsChecked' (type 'IReference1').
I am not pleased with this. So let's work around it with an attached property.
public class NullableCheckbox : DependencyObject
{
public static bool GetEnabled(DependencyObject obj)
{ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value)
{ obj.SetValue(EnabledProperty, value); }
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(NullableCheckbox), new PropertyMetadata(false, EnabledChanged));
private static void EnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var checkbox = d as CheckBox;
if ((bool)e.NewValue)
{
var binding = new Binding
{
Path = new PropertyPath("IsChecked"),
Mode = BindingMode.TwoWay,
Source = checkbox,
};
checkbox.SetBinding(NullableCheckbox.InternalStateProperty, binding);
}
}
private static object GetInternalState(DependencyObject obj)
{ return (object)obj.GetValue(InternalStateProperty); }
private static void SetInternalState(DependencyObject obj, object value)
{ obj.SetValue(InternalStateProperty, value); }
private static readonly DependencyProperty InternalStateProperty =
DependencyProperty.RegisterAttached("InternalState", typeof(object),
typeof(NullableCheckbox), new PropertyMetadata(null, InternalStateChanged));
private static void InternalStateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ SetIsChecked(d, (object)e.NewValue); }
public static object GetIsChecked(DependencyObject obj)
{ return (object)obj.GetValue(IsCheckedProperty); }
public static void SetIsChecked(DependencyObject obj, object value)
{ obj.SetValue(IsCheckedProperty, value); }
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(object),
typeof(NullableCheckbox), new PropertyMetadata(default(object), IsCheckedChanged));
private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var checkbox = d as CheckBox;
bool? newvalue = null;
if (e.NewValue is bool?)
newvalue = (bool?)e.NewValue;
else if (e.NewValue != null)
{
bool newbool;
if (!bool.TryParse(e.NewValue.ToString(), out newbool))
return;
newvalue = newbool;
}
if (!checkbox.IsChecked.Equals(newvalue))
checkbox.IsChecked = newvalue;
}
}
Your XAML would only change like this:
<CheckBox Content="Hello three-state"
IsThreeState="True"
local:NullableCheckbox.Enabled="true"
local:NullableCheckbox.IsChecked="{Binding State, Mode=TwoWay}" />
The regular IsChecked property doesn't matter, it will be overwritten by the attached property. Your viewmodel can stay the same. It's real magic, huh?
Best of luck!

Categories

Resources