Add MouseMove event if value is true - c#

I have DataTemplate selector for my ItemsControl and I'd like to achieve something like:
<DataTemplate x:Key="buttonTemplate">
<Button if(someValue = true -> add thisPreviewMouseUp="button_MouseUp") PreviewMouseLeftButtonDown="button_MouseLeftButtonUp" PreviewMouseMove="button_MouseMove" Click="b_Click">
<Button.Content>
<Image Source="sample.png" Height="{Binding height}" Width="{Binding width}" Stretch="Fill" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Button.Content>
<Button.RenderTransform>
<RotateTransform Angle="{Binding angle}" />
</Button.RenderTransform>
</Button>
</DataTemplate>
Users can move, change size of buttons from manager mode, but I don't want to fire this event in normal mode (now there is if(_fromWhere == "MANAGER") in mouse_move event)
Any idea how can I make it work?
Thanks!

Might help.
Mvvm Approach
I think easy way of doing this , Enable the Event only when a condition is true.
what i'm doing here i Execute a a Command Named DropUser when Event Drop Occurs. I used mvvmlight for easy commanding . you can use ICommand Wpf default commanding reference
xmlns:interactivity="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mvvmlight="http://www.galasoft.ch/mvvmlight"
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Drop">
<mvvmlight:EventToCommand
Command="{Binding DataContext.DropUser,
PassEventArgsToCommand="True"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
ViewModel
#region RelayCommand
private RelayCommand<DragEventArgs> _dropUser;
public RelayCommand<DragEventArgs> DropUser
{
get
{
return _dropUser ?? (_dropUser = new RelayCommand<DragEventArgs>(DropMethod,canExecute));
}
}
private bool canExecute(DragEventArgs arg)
{
// check your condition return true . Command is only work when you return true.
}
#endregion
// Method Will Fire here and do action here
private void DropMethod(DragEventArgs eventArgs)
{
if (eventArgs != null)
{
}
}
what you need is like this.
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="PreviewMouseLeftButtonDown">
<mvvmlight:EventToCommand
Command="{Binding CommandName,
PassEventArgsToCommand="True"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
Viewmodel
private RelayCommand<RoutedEventArgs> _commandName;
public RelayCommand<RoutedEventArgs>CommandName
{
get
{
return commandName ?? (commandName = new RelayCommand<RoutedEventArgs>(invokeAction, canExecuteMethod));
}
}
private bool canExecuteMethod()
{
check your condition return true
}
private void invokeAction(RoutedEventArgs event)
{
action you want to do
}

I don't know, if it's possible in XAML to set event to be fired or not.
However you can set someValue in Tag property of button and then in fired event check for Tag as proposed in this link.
<Button Tag="{Binding someValue}" ...>
In event handler:
bool someValue = (bool)(((Button)sender).Tag);
Edit 1:
An alternative way would be to get the DataContext of button if someValue is part of DataContext of button:
var dataContext = ((Button)e.OriginalSource).DataContext;
if (dataContext.someValue)
{
...
}
Edit 2:
After a little investigation I found out that it is possible to add MouseMove event depending on someValue. Unfortunately it is unnecessarily complex I think.
<Button Style="{Binding someValue, Converter={StaticResource BoolToButtonStyleConverter}, ConverterParameter={StaticResource MousePreviewEventSetStyle}, Mode=OneWay}" ...>
In Window.Resources define this:
<l:BoolToButtonStyleConverter x:Key="BoolToButtonStyleConverter" />
<Style x:Key="MousePreviewEventSetStyle" TargetType="{x:Type Button}" >
<EventSetter Event="PreviewMouseUp" Handler="PreviewMouseUpClicked" />
</Style>
And then in Application.Resources define this:
<Style x:Key="clearStyle" TargetType="{x:Type Button}" />
After all you have to define:
public class BoolToButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
bool someValue = (bool)value;
if (someValue)
return parameter;
else
return Application.Current.Resources["clearStyle"];
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Finally set the handler for event:
void PreviewMouseUpClicked(object sender, RoutedEventArgs e)
{
...
}
Memo: Probably there is better way to set "default style" to button that define "clear style".

Related

How to Toggle Visibility Between a Button and a Stack Panel Containing Two Buttons

I am having difficulty figuring out how to toggle the visibility between three buttons. Here is the scenario:
I have 3 buttons on a user control, an Edit button, an OK button, and a Cancel button.
The Ok and Cancel buttons are grouped together in a stack panel.
The Edit button is by itself.
I would like when the Edit button is pressed, that it (the Edit button) is hidden and the stack panel containing the Ok and Cancel buttons are shown.
When either the Cancel or Ok buttons are pressed, they are hidden, and the Edit button is shown again.
There will be 7 lines on this form that are all very similar, with a label, text box, and an edit button.
Is it possible to use only a few methods to control the visibility of all of the buttons/ stack panels.
i.e. Can I have one Edit method and show the stack panel depending on the text box control name/ binding, instead of having to right 7 methods for showing the stack panel, 7 Ok methods and 7 cancel methods?
Here is the line on the form with the Edit button:
And here is the line on the form with the Ok and Cancel buttons:
Here is the XAML code that I've come up with for this line:
<StackPanel
Orientation="Horizontal"
HorizontalAlignment="Center"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="4">
<Label
Style="{StaticResource DeviceInfoPropertyLabelStyle}">
CONTROLLER NAME:
</Label>
<TextBox
Text="{Binding ControllerName}"
Style="{StaticResource DeviceInfoTextBoxStyle}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel
Orientation="Horizontal"
Grid.Column="0">
<Button
Command="{Binding EditCommand}"
Visibility="{Binding IsEditButtonVisible, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Collapsed}"
Style="{StaticResource DeviceInfoEditButtonStyle}">
Edit
</Button>
</StackPanel>
<StackPanel
x:Name="EditControllerNameStackPanel"
Orientation="Horizontal"
Grid.Column="0"
Visibility="{Binding IsOkCancelButtonVisible, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Visible}">
<Button
Command="{Binding OkEditCommand}"
Style="{StaticResource DeviceInfoEditOkButtonStyle}">
OK
</Button>
<Button
Command="{Binding CancelEditCommand}"
Style="{StaticResource DeviceInfoEditCancelButtonStyle}">
CANCEL
</Button>
</StackPanel>
</Grid>
</StackPanel>
Here is the code in the ViewModel that I have so far. It's only a skeleton at this point:
public bool IsEditButtonVisible
{
get
{
bool output = false;
if (true)
{
return true;
}
return output;
}
}
public bool IsOkCancelButtonVisible
{
get => true;
}
[RelayCommand]
private void Edit()
{
if (true)
{
}
}
[RelayCommand]
private void OkEdit()
{
}
[RelayCommand]
private void CancelEdit()
{
}
Note that I am using the MVVM Community Toolkit.
Let me know if I need to provide any additional information.
Thanks
You can use just one boolean to toggle the visibility. Change BoolToVisibilityConverter code a bit..
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b)
{
if (parameter is string str && str == "Inverse")
return b ? Visibility.Hidden : Visibility.Visible;
return b ? Visibility.Visible : Visibility.Hidden;
}
return Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Edit button
<Button
Visibility="{Binding IsEditButtonVisible, Converter={StaticResource BoolToVisibilityConverter}}">
OK/Cancel stackpanel
<StackPanel
Visibility="{Binding IsEditButtonVisible, Converter={StaticResource BoolToVisibilityConverter}, ConverterParameter=Inverse}">
ViewModel (sorry I don't use MVVM Community Toolkit)
public class ViewModel : INotifyPropertyChanged
{
private bool _isEditButtonVisible;
public bool IsEditButtonVisible
{
set
{
_isEditButtonVisible = value;
OnPropertyChanged(nameof(IsEditButtonVisible));
}
get => _isEditButtonVisible;
}
private void Edit()
{
IsEditButtonVisible = false;
}
private void Ok()
{
IsEditButtonVisible = true;
}
private void Cancel()
{
IsEditButtonVisible = true;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// other code
}
To apply the same logic over the other 7 similar groups, you must have at least 7 booleans, but you can have 3 commands only, where you can pass the name of the group to the command, and the command will toggle the appropriate group based on the passed parameter
In View
<Button
Command="{Binding OkCommand}"
CommandParameter="group1">
OK
</Button>
In ViewModel
private void Edit(string commandParameter)
{
IsEdit1ButtonVisible = commandParameter != "group1";
IsEdit2ButtonVisible = commandParameter != "group2";
IsEdit3ButtonVisible = commandParameter != "group3";
// etc...
}
private void Ok(string commandParameter)
{
IsEdit1ButtonVisible = commandParameter == "group1";
IsEdit2ButtonVisible = commandParameter == "group2";
IsEdit3ButtonVisible = commandParameter == "group3";
// etc...
}
private void Cancel(string commandParameter)
{
IsEdit1ButtonVisible = commandParameter == "group1";
IsEdit2ButtonVisible = commandParameter == "group2";
IsEdit3ButtonVisible = commandParameter == "group3";
// etc...
}

Show a control on DataGridRow Mouse Over in WPF

It would be very convenient to show data of a specific item only when the user hovers its row in a static DataGrid. What I tried yet is: link MouseEnter and MouseLeave events to methods that will save the Index of the currently Hovered item, create a converter that will compare if the item index is the hovered item index (if yes show, otherwise hide), and finally call static INPC on the static DataGrid ItemSource, but it seems I am still missing something. (I'm coding in MVVM pattern but since this is strictly View-oriented I have no problem if the solution involves code-behind)
tl;dr: I wanna achieve this:
Here's the full code of what I did yet for easy testing :
View
<Window x:Class="MouseOverDataGridRow.MainWindow"
[...] >
<!-- Converter Section -->
<Window.Resources>
<local:HoveredRowToVisibilityConverter x:Key="HoveredRowToVisibility"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding MyList, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False" CanUserAddRows="False">
<!-- Events Section -->
<DataGrid.ItemContainerStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseEnter" Handler="DataGridRow_MouseEnter" />
<EventSetter Event="MouseLeave" Handler="DataGridRow_MouseLeave" />
</Style>
</DataGrid.ItemContainerStyle>
<!-- Always Visible Data -->
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- On Mouse Over Only Data -->
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="Hidden Data" Visibility="{Binding Converter={StaticResource HoveredRowToVisibility}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code-behind :
public partial class MainWindow : Window
{
// Static INPC (maybe is the problem?)
public static event PropertyChangedEventHandler StaticPropertyChanged;
private static void NotifyStaticPropertyChanged([CallerMemberName] string name = null)
{
StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(name));
}
// Hovered Row Index
public static int HoveredRowID { get; set; } = -1;
// DataGrid's ItemSource
private static ObservableCollection<int> _myList = new ObservableCollection<int>() { 1, 2, 3, 4, 5 };
public static ObservableCollection<int> MyList
{
get { return _myList; }
set { _myList = value;
NotifyStaticPropertyChanged();
}
}
// Constructor
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
// Method that retrieves the id of the Hovered Row and calls INPC. The retrieving part is working, but the View isn't updating
private void DataGridRow_MouseEnter(object sender, MouseEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
if (row.Item is int)
{
int item = (int)row.Item;
HoveredRowID = MyList.IndexOf(item);
NotifyStaticPropertyChanged("MyList");
}
}
// Method that resests the Hovered Row and call INPC to hide the previously shown stuff
private void DataGridRow_MouseLeave(object sender, MouseEventArgs e)
{
HoveredRowID = -1;
NotifyStaticPropertyChanged("MyList");
}
}
// The converter
public class HoveredRowToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
int item = (int)value;
// Check if this item is the hovered item
if (MainWindow.MyList.IndexOf(item) == MainWindow.HoveredRowID)
return Visibility.Visible;
else
return Visibility.Hidden;
}
else
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
#Mitch's instructions are correct, Binding Button.Visibility To DataGridRow's IsMouseOver is the solution. However an important detail to be able to do that is to use a DataTrigger to do the Binding, and set the default Visbility to Hidden/Collapsed. Here I used DataTemplate.Triggers but it can also be done in Button.Triggers
<DataTemplate>
<Button x:Name="HiddenDeleteButton" Text="Delete" Visibility="Hidden" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsMouseOver, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Value="True">
<Setter Property="Visibility" TargetName="HiddenDeleteButton" Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
I think you can just bind Button.Visibility to IsMouseOver on DataGridRow. I don’t think there is any need to get fancy (or have anything in code behind).

how to get selected radiobutton in viewmodel from stackpanel [duplicate]

EDIT: Problem was fixed in .NET 4.0.
I have been trying to bind a group of radio buttons to a view model using the IsChecked button. After reviewing other posts, it appears that the IsChecked property simply doesn't work. I have put together a short demo that reproduces the problem, which I have included below.
Here is my question: Is there a straightforward and reliable way to bind radio buttons using MVVM? Thanks.
Additional information: The IsChecked property doesn't work for two reasons:
When a button is selected, the IsChecked properties of other buttons in the group don't get set to false.
When a button is selected, its own IsChecked property does not get set after the first time the button is selected. I am guessing that the binding is getting trashed by WPF on the first click.
Demo project: Here is the code and markup for a simple demo that reproduces the problem. Create a WPF project and replace the markup in Window1.xaml with the following:
<Window x:Class="WpfApplication1.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" Loaded="Window_Loaded">
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" />
</StackPanel>
</Window>
Replace the code in Window1.xaml.cs with the following code (a hack), which sets the view model:
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Window1ViewModel();
}
}
}
Now add the following code to the project as Window1ViewModel.cs:
using System.Windows;
namespace WpfApplication1
{
public class Window1ViewModel
{
private bool p_ButtonAIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonAIsChecked
{
get { return p_ButtonAIsChecked; }
set
{
p_ButtonAIsChecked = value;
MessageBox.Show(string.Format("Button A is checked: {0}", value));
}
}
private bool p_ButtonBIsChecked;
/// <summary>
/// Summary
/// </summary>
public bool ButtonBIsChecked
{
get { return p_ButtonBIsChecked; }
set
{
p_ButtonBIsChecked = value;
MessageBox.Show(string.Format("Button B is checked: {0}", value));
}
}
}
}
To reproduce the problem, run the app and click Button A. A message box will appear, saying that Button A's IsChecked property has been set to true. Now select Button B. Another message box will appear, saying that Button B's IsChecked property has been set to true, but there is no message box indicating that Button A's IsChecked property has been set to false--the property hasn't been changed.
Now click Button A again. The button will be selected in the window, but no message box will appear--the IsChecked property has not been changed. Finally, click on Button B again--same result. The IsChecked property is not updated at all for either button after the button is first clicked.
If you start with Jason's suggestion then the problem becomes a single bound selection from a list which translates very nicely to a ListBox. At that point it's trivial to apply styling to a ListBox control so that it shows up as a RadioButton list.
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton Content="{TemplateBinding Content}"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
Looks like they fixed binding to the IsChecked property in .NET 4. A project that was broken in VS2008 works in VS2010.
For the benefit of anyone researching this question down the road, here is the solution I ultimately implemented. It builds on John Bowen's answer, which I selected as the best solution to the problem.
First, I created a style for a transparent list box containing radio buttons as items. Then, I created the buttons to go in the list box--my buttons are fixed, rather than read into the app as data, so I hard-coded them into the markup.
I use an enum called ListButtons in the view model to represent the buttons in the list box, and I use each button's Tag property to pass a string value of the enum value to use for that button. The ListBox.SelectedValuePath property allows me to specify the Tag property as the source for the selected value, which I bind to the view model using the SelectedValue property. I thought I would need a value converter to convert between the string and its enum value, but WPF's built-in converters handled the conversion without problem.
Here is the complete markup for Window1.xaml:
<Window x:Class="RadioButtonMvvmDemo.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">
<!-- Resources -->
<Window.Resources>
<Style x:Key="RadioButtonList" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<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">
<RadioButton
Focusable="False"
IsHitTestVisible="False"
IsChecked="{TemplateBinding IsSelected}">
<ContentPresenter />
</RadioButton>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<Border BorderThickness="0" Padding="0" BorderBrush="Transparent" Background="Transparent" Name="Bd" SnapsToDevicePixels="True">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<!-- Layout -->
<Grid>
<!-- Note that we use SelectedValue, instead of SelectedItem. This allows us
to specify the property to take the value from, using SelectedValuePath. -->
<ListBox Style="{StaticResource RadioButtonList}" SelectedValuePath="Tag" SelectedValue="{Binding Path=SelectedButton}">
<ListBoxItem Tag="ButtonA">Button A</ListBoxItem>
<ListBoxItem Tag="ButtonB">Button B</ListBoxItem>
</ListBox>
</Grid>
</Window>
The view model has a single property, SelectedButton, which uses a ListButtons enum to show which button is selected. The property calls an event in the base class I use for view models, which raises the PropertyChanged event:
namespace RadioButtonMvvmDemo
{
public enum ListButtons {ButtonA, ButtonB}
public class Window1ViewModel : ViewModelBase
{
private ListButtons p_SelectedButton;
public Window1ViewModel()
{
SelectedButton = ListButtons.ButtonB;
}
/// <summary>
/// The button selected by the user.
/// </summary>
public ListButtons SelectedButton
{
get { return p_SelectedButton; }
set
{
p_SelectedButton = value;
base.RaisePropertyChangedEvent("SelectedButton");
}
}
}
}
In my production app, the SelectedButton setter will call a service class method that will take the action required when a button is selected.
And to be complete, here is the base class:
using System.ComponentModel;
namespace RadioButtonMvvmDemo
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Protected Methods
/// <summary>
/// Raises the PropertyChanged event.
/// </summary>
/// <param name="propertyName">The name of the changed property.</param>
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
#endregion
}
}
Hope that helps!
One solution is to update the ViewModel for the radio buttons in the setter of the properties. When Button A is set to True, set Button B to false.
Another important factor when binding to an object in the DataContext is that the object should implement INotifyPropertyChanged. When any bound property changes, the event should be fired and include the name of the changed property. (Null check omitted in the sample for brevity.)
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool _ButtonAChecked = true;
public bool ButtonAChecked
{
get { return _ButtonAChecked; }
set
{
_ButtonAChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonAChecked"));
if (value) ButtonBChecked = false;
}
}
protected bool _ButtonBChecked;
public bool ButtonBChecked
{
get { return _ButtonBChecked; }
set
{
_ButtonBChecked = value;
PropertyChanged(this, new PropertyChangedEventArgs("ButtonBChecked"));
if (value) ButtonAChecked = false;
}
}
}
Edit:
The issue is that when first clicking on Button B the IsChecked value changes and the binding feeds through, but Button A does not feed through its unchecked state to the ButtonAChecked property. By manually updating in code the ButtonAChecked property setter will get called the next time Button A is clicked.
Here is another way you can do it
VIEW:
<StackPanel Margin="90,328,965,389" Orientation="Horizontal">
<RadioButton Content="Mr" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Mrs" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Ms" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}, Mode=TwoWay}" GroupName="Title"/>
<RadioButton Content="Other" Command="{Binding TitleCommand, Mode=TwoWay}" CommandParameter="{Binding Content, RelativeSource={RelativeSource Mode=Self}}" GroupName="Title"/>
<TextBlock Text="{Binding SelectedTitle, Mode=TwoWay}"/>
</StackPanel>
ViewModel:
private string selectedTitle;
public string SelectedTitle
{
get { return selectedTitle; }
set
{
SetProperty(ref selectedTitle, value);
}
}
public RelayCommand TitleCommand
{
get
{
return new RelayCommand((p) =>
{
selectedTitle = (string)p;
});
}
}
Not sure about any IsChecked bugs, one possible refactor you could make to your viewmodel:the view has a number of mutually exclusive states represented by a series of RadioButtons, only one of which at any given time can be selected. In the view model, just have 1 property (e.g. an enum) which represents the possible states: stateA, stateB, etc That way you wouldn't need all the individual ButtonAIsChecked, etc
A small extension to John Bowen's answer: It doesn't work when the values don't implement ToString(). What you need instead of setting the Content of the RadioButton to a TemplateBinding, just put a ContentPresenter in it, like this:
<ListBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<RadioButton IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsSelected}">
<ContentPresenter/>
</RadioButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
This way you can additionally use DisplayMemberPath or an ItemTemplate as appropriate. The RadioButton just "wraps" the items, providing the selection.
I know this is an old question and the original issue was resolved in .NET 4. and in all honesty this is slightly off topic.
In most cases where I've wanted to use RadioButtons in MVVM it's to select between elements of an enum, this requires binding a bool property in the VM space to each button and using them to set an overall enum property that reflects the actual selection, this gets very tedious very quick. So I came up with a solution that is re-usable and very easy to implement, and does not require ValueConverters.
The View is pretty much the same, but once you have your enum in place the VM side can be done with a single property.
MainWindowVM
using System.ComponentModel;
namespace EnumSelectorTest
{
public class MainWindowVM : INotifyPropertyChanged
{
public EnumSelectorVM Selector { get; set; }
private string _colorName;
public string ColorName
{
get { return _colorName; }
set
{
if (_colorName == value) return;
_colorName = value;
RaisePropertyChanged("ColorName");
}
}
public MainWindowVM()
{
Selector = new EnumSelectorVM
(
typeof(MyColors),
MyColors.Red,
false,
val => ColorName = "The color is " + ((MyColors)val).ToString()
);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The class that does all the work inherits from DynamicObject. Viewed from the outside it creates a bool property for each element in the enum prefixed with 'Is', 'IsRed', 'IsBlue' etc. that can be bound to from XAML. Along with a Value property that holds the actual enum value.
public enum MyColors
{
Red,
Magenta,
Green,
Cyan,
Blue,
Yellow
}
EnumSelectorVM
using System;
using System.ComponentModel;
using System.Dynamic;
using System.Linq;
namespace EnumSelectorTest
{
public class EnumSelectorVM : DynamicObject, INotifyPropertyChanged
{
//------------------------------------------------------------------------------------------------------------------------------------------
#region Fields
private readonly Action<object> _action;
private readonly Type _enumType;
private readonly string[] _enumNames;
private readonly bool _notifyAll;
#endregion Fields
//------------------------------------------------------------------------------------------------------------------------------------------
#region Properties
private object _value;
public object Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
RaisePropertyChanged("Value");
_action?.Invoke(_value);
}
}
#endregion Properties
//------------------------------------------------------------------------------------------------------------------------------------------
#region Constructor
public EnumSelectorVM(Type enumType, object initialValue, bool notifyAll = false, Action<object> action = null)
{
if (!enumType.IsEnum)
throw new ArgumentException("enumType must be of Type: Enum");
_enumType = enumType;
_enumNames = enumType.GetEnumNames();
_notifyAll = notifyAll;
_action = action;
//do last so notification fires and action is executed
Value = initialValue;
}
#endregion Constructor
//------------------------------------------------------------------------------------------------------------------------------------------
#region Methods
//---------------------------------------------------------------------
#region Public Methods
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
{
result = null;
return false;
}
try
{
result = Value.Equals(Enum.Parse(_enumType, elementName));
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
result = null;
return false;
}
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object newValue)
{
if (!(newValue is bool))
return false;
string elementName;
if (!TryGetEnumElemntName(binder.Name, out elementName))
return false;
try
{
if((bool) newValue)
Value = Enum.Parse(_enumType, elementName);
}
catch (Exception ex) when (ex is ArgumentNullException || ex is ArgumentException || ex is OverflowException)
{
return false;
}
if (_notifyAll)
foreach (var name in _enumNames)
RaisePropertyChanged("Is" + name);
else
RaisePropertyChanged("Is" + elementName);
return true;
}
#endregion Public Methods
//---------------------------------------------------------------------
#region Private Methods
private bool TryGetEnumElemntName(string bindingName, out string elementName)
{
elementName = "";
if (bindingName.IndexOf("Is", StringComparison.Ordinal) != 0)
return false;
var name = bindingName.Remove(0, 2); // remove first 2 chars "Is"
if (!_enumNames.Contains(name))
return false;
elementName = name;
return true;
}
#endregion Private Methods
#endregion Methods
//------------------------------------------------------------------------------------------------------------------------------------------
#region Events
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion Events
}
}
To respond to changes you can either subscribe to the NotifyPropertyChanged event or pass an anonymous method to the constructor as done above.
And finally the MainWindow.xaml
<Window x:Class="EnumSelectorTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<RadioButton IsChecked="{Binding Selector.IsRed}">Red</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsMagenta}">Magenta</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsBlue}">Blue</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsCyan}">Cyan</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsGreen}">Green</RadioButton>
<RadioButton IsChecked="{Binding Selector.IsYellow}">Yellow</RadioButton>
<TextBlock Text="{Binding ColorName}"/>
</StackPanel>
</Grid>
</Window>
Hope someone else finds this useful, 'cause I reckon this ones going in my toolbox.
You have to add the Group Name for the Radio button
<StackPanel>
<RadioButton Content="Button A" IsChecked="{Binding Path=ButtonAIsChecked, Mode=TwoWay}" GroupName="groupName" />
<RadioButton Content="Button B" IsChecked="{Binding Path=ButtonBIsChecked, Mode=TwoWay}" GroupName="groupName" />
</StackPanel>
I have a very similar problem in VS2015 and .NET 4.5.1
XAML:
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="6" Rows="1"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate >
<RadioButton GroupName="callGroup" Style="{StaticResource itemListViewToggle}" Click="calls_ItemClick" Margin="1" IsChecked="{Binding Path=Selected,Mode=TwoWay}" Unchecked="callGroup_Checked" Checked="callGroup_Checked">
....
As you can see in this code i have a listview, and items in template are radiobuttons that belongs to a groupname.
If I add a new item to the collection with the property Selected set to True it appears checked and the rest of buttons remain checked.
I solve it by getting the checkedbutton first and set it to false manually but this is not the way it's supposed to be done.
code behind:
`....
lstInCallList.ItemsSource = ContactCallList
AddHandler ContactCallList.CollectionChanged, AddressOf collectionInCall_change
.....
Public Sub collectionInCall_change(sender As Object, e As NotifyCollectionChangedEventArgs)
'Whenever collection change we must test if there is no selection and autoselect first.
If e.Action = NotifyCollectionChangedAction.Add Then
'The solution is this, but this shouldn't be necessary
'Dim seleccionado As RadioButton = getCheckedRB(lstInCallList)
'If seleccionado IsNot Nothing Then
' seleccionado.IsChecked = False
'End If
DirectCast(e.NewItems(0), PhoneCall).Selected = True
.....
End sub
`
<RadioButton IsChecked="{Binding customer.isMaleFemale}">Male</RadioButton>
<RadioButton IsChecked="{Binding customer.isMaleFemale,Converter= {StaticResource GenderConvertor}}">Female</RadioButton>
Below is the code for IValueConverter
public class GenderConvertor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return !(bool)value;
}
}
this worked for me. Even value got binded on both view and viewmodel according to the radio button click. True--> Male and False-->Female

WPF&MVVM: access to Controls from RelayCommand()

I need both operating by mouse clicking and operating by hotkeys in my WPF application. User's actions affects on both data and appearance of application controls.
For example, the following app will send data to tea machine. You can select the tea brand, type (hot or cold) and optional ingredients: milk, lemon and syrup.
Not good from the point of view of UI design, but just example:
If to click the dropdown menu or input Ctrl+B, the list of select options will appear.
If to click the "Hot" button on input Ctrl+T, button becomes blue and text becomes "Cold". If to click or input Ctrl+T again, button becomes orange and text becomes to "Hot" again.
If to click optional ingredient button or input respective shortcut, button's background and text becomes gray (it means "unselected"). Same action will return the respective button to active state.
If don't use MVVM and don't define shortcuts, the logic will be relatively simple:
Tea tea = new Tea(); // Assume that default settings avalible
private void ToggleTeaType(object sender, EventArgs e){
// Change Data
if(tea.getType().Equals("Hot")){
tea.setType("Cold");
}
else{
tea.setType("Hot");
}
// Change Button Appearence
ChangeTeaTypeButtonAppearence(sender, e);
}
private void ChangeTeaTypeButtonAppearence(object sender, EventArgs e){
Button clickedButton = sender as Button;
Style hotTeaButtonStyle = this.FindResource("TeaTypeButtonHot") as Style;
Style coldTeaButtonStyle = this.FindResource("TeaTypeButtonCold") as Style;
if (clickedButton.Tag.Equals("Hot")) {
clickedButton.Style = coldTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Cold";
}
else (clickedButton.Tag.Equals("Cold")) {
clickedButton.Style = hotTeaButtonStyle; // includes Tag declaration
clickedButton.Content = "Hot";
}
}
// similarly for ingredients toggles
XAML:
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{StaticResource TeaTypeButtonHot}"/>
<Button Content="Milk"
Tag="True"
Click="ToggleMilk"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Lemon"
Tag="True"
Click="ToggleLemon"
Style="{StaticResource IngredientButtonTrue}"/>
<Button Content="Syrup"
Tag="True"
Click="ToggleSyrup"
Style="{StaticResource IngredientButtonTrue}"/>
I changed my similar WPF project to MVVM because thanks to commands it's simple to assign the shortcuts:
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding ToggleTeaType}" />
</Window.InputBindings>
However, now it's a problem how to set the control's appearance. The following code is invalid:
private RelayCommand toggleTeaType;
public RelayCommand ToggleTeaType {
// change data by MVVM methods...
// change appearence:
ChangeTeaTypeButtonAppearence(object sender, EventArgs e);
}
I need the Relay Commands because I can bind it to both buttons and shortcuts, but how I can access to View controls from RelayCommand?
You should keep the viewmodel clean of view specific behavior. The viewmodel should just provide an interface for all relevant settings, it could look similar to the following (BaseViewModel would contain some helper methods to implement INotifyPropertyChanged etc.):
public class TeaConfigurationViewModel : BaseViewModel
{
public TeaConfigurationViewModel()
{
_TeaNames = new string[]
{
"Lipton",
"Generic",
"Misc",
};
}
private IEnumerable<string> _TeaNames;
public IEnumerable<string> TeaNames
{
get { return _TeaNames; }
}
private string _SelectedTea;
public string SelectedTea
{
get { return _SelectedTea; }
set { SetProperty(ref _SelectedTea, value); }
}
private bool _IsHotTea;
public bool IsHotTea
{
get { return _IsHotTea; }
set { SetProperty(ref _IsHotTea, value); }
}
private bool _WithMilk;
public bool WithMilk
{
get { return _WithMilk; }
set { SetProperty(ref _WithMilk, value); }
}
private bool _WithLemon;
public bool WithLemon
{
get { return _WithLemon; }
set { SetProperty(ref _WithLemon, value); }
}
private bool _WithSyrup;
public bool WithSyrup
{
get { return _WithSyrup; }
set { SetProperty(ref _WithSyrup, value); }
}
}
As you see, there is a property for each setting, but the viewmodel doesn't care about how the property is assigned.
So lets build some UI. For the following example, generally suppose xmlns:local points to your project namespace.
I suggest utilizing a customized ToggleButton for your purpose:
public class MyToggleButton : ToggleButton
{
static MyToggleButton()
{
MyToggleButton.DefaultStyleKeyProperty.OverrideMetadata(typeof(MyToggleButton), new FrameworkPropertyMetadata(typeof(MyToggleButton)));
}
public Brush ToggledBackground
{
get { return (Brush)GetValue(ToggledBackgroundProperty); }
set { SetValue(ToggledBackgroundProperty, value); }
}
// Using a DependencyProperty as the backing store for ToggledBackground. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ToggledBackgroundProperty =
DependencyProperty.Register("ToggledBackground", typeof(Brush), typeof(MyToggleButton), new FrameworkPropertyMetadata());
}
And in Themes/Generic.xaml:
<Style TargetType="{x:Type local:MyToggleButton}" BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyToggleButton}">
<Border x:Name="border1" BorderBrush="Gray" BorderThickness="1" Background="{TemplateBinding Background}" Padding="5">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="border1" Property="Background" Value="{Binding ToggledBackground,RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, build the actual window content using this toggle button. This is just a rough sketch of your desired UI, containing only the functional controls without labels and explanation:
<Grid x:Name="grid1">
<StackPanel>
<StackPanel Orientation="Horizontal">
<ComboBox
x:Name="cb1"
VerticalAlignment="Center"
IsEditable="True"
Margin="20"
MinWidth="200"
ItemsSource="{Binding TeaNames}"
SelectedItem="{Binding SelectedTea}">
</ComboBox>
<local:MyToggleButton
x:Name="hotToggle"
IsChecked="{Binding IsHotTea}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="AliceBlue" ToggledBackground="Orange">
<local:MyToggleButton.Style>
<Style TargetType="{x:Type local:MyToggleButton}">
<Setter Property="Content" Value="Cold"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Hot"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyToggleButton.Style>
</local:MyToggleButton>
</StackPanel>
<StackPanel Orientation="Horizontal">
<local:MyToggleButton
x:Name="milkToggle"
Content="Milk"
IsChecked="{Binding WithMilk}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="lemonToggle"
Content="Lemon"
IsChecked="{Binding WithLemon}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
<local:MyToggleButton
x:Name="syrupToggle"
Content="Syrup"
IsChecked="{Binding WithSyrup}"
VerticalAlignment="Center"
Margin="20" MinWidth="60"
Background="WhiteSmoke" ToggledBackground="LightGreen"/>
</StackPanel>
</StackPanel>
</Grid>
Notice the style trigger to change the button content between Hot and Cold.
Initialize the datacontext somewhere (eg. in the window constructor)
public MainWindow()
{
InitializeComponent();
grid1.DataContext = new TeaConfigurationViewModel();
}
At this point, you have a fully functional UI, it will work with the default mouse and keyboard input methods, but it won't yet support your shortcut keys.
So lets add the keyboard shortcuts without destroying the already-working UI. One approach is, to create and use some custom commands:
public static class AutomationCommands
{
public static RoutedCommand OpenList = new RoutedCommand("OpenList", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.B, ModifierKeys.Control)
});
public static RoutedCommand ToggleHot = new RoutedCommand("ToggleHot", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.T, ModifierKeys.Control)
});
public static RoutedCommand ToggleMilk = new RoutedCommand("ToggleMilk", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.M, ModifierKeys.Control)
});
public static RoutedCommand ToggleLemon = new RoutedCommand("ToggleLemon", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.L, ModifierKeys.Control)
});
public static RoutedCommand ToggleSyrup = new RoutedCommand("ToggleSyrup", typeof(AutomationCommands), new InputGestureCollection()
{
new KeyGesture(Key.S, ModifierKeys.Control)
});
}
You can then bind those commands to appropriate actions in your main window:
<Window.CommandBindings>
<CommandBinding Command="local:AutomationCommands.OpenList" Executed="OpenList_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleHot" Executed="ToggleHot_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleMilk" Executed="ToggleMilk_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleLemon" Executed="ToggleLemon_Executed"/>
<CommandBinding Command="local:AutomationCommands.ToggleSyrup" Executed="ToggleSyrup_Executed"/>
</Window.CommandBindings>
and implement the appropriate handler method for each shortcut in the window code behind:
private void OpenList_Executed(object sender, ExecutedRoutedEventArgs e)
{
FocusManager.SetFocusedElement(cb1, cb1);
cb1.IsDropDownOpen = true;
}
private void ToggleHot_Executed(object sender, ExecutedRoutedEventArgs e)
{
hotToggle.IsChecked = !hotToggle.IsChecked;
}
private void ToggleMilk_Executed(object sender, ExecutedRoutedEventArgs e)
{
milkToggle.IsChecked = !milkToggle.IsChecked;
}
private void ToggleLemon_Executed(object sender, ExecutedRoutedEventArgs e)
{
lemonToggle.IsChecked = !lemonToggle.IsChecked;
}
private void ToggleSyrup_Executed(object sender, ExecutedRoutedEventArgs e)
{
syrupToggle.IsChecked = !syrupToggle.IsChecked;
}
Again, remember this whole input binding thing is purely UI related, it is just an alternative way to change the displayed properties and the changes will be transferred to the viewmodel with the same binding as if the user clicks the button by mouse. There is no reason to carry such things into the viewmodel.
how I can access to View controls from RelayCommand?
You shouldn't. The whole point of MVVM (arguably) is to separate concerns. The 'state' that the ViewModel contains is rendered by the View (controls). The ViewModel/logic should never directly adjust the view - as this breaks the separation of concerns and closely couples the logic to the rendering.
What you need is for the view to render how it wants to display the state in the View Model.
Typically, this is done by bindings. As example: Rather than the ViewModel grabbing a text box reference and setting the string: myTextBox.SetText("some value"), we have the view bind to the property MyText in the view model.
It's the view's responsibility to decide how to show things on the screen.
That's all well and good, but how? I suggest, if you want to do this change using styles like you describe, I'd try using a converter that converts the using a binding to ViewModel state (Say, an enum property Hot or Cold):
<Button Content="Hot"
Tag="Hot"
Click="ToggleTeaType"
Style="{Binding TeaType, Converter={StaticResource TeaTypeButtonStyleConverter}}"/>
Note, we're using WPF's bindings. The only reference we've got tot he view model is through it's property TeaType.
Defined in your static resources, we have the converter:
<ResourceDictionary>
<Style x:Key="HotTeaStyle"/>
<Style x:Key="ColdTeaStyle"/>
<local:TeaTypeButtonStyleConverter
x:Key="TeaTypeButtonStyleConverter"
HotStateStyle="{StaticResource HotTeaStyle}"
ColdStateStyle="{StaticResource ColdTeaStyle}"/>
</ResourceDictionary>
And have the logic for converting from the TeaType enum to a Style in this:
public enum TeaType
{
Hot, Cold
}
class TeaTypeButtonStyleConverter : IValueConverter
{
public Style HotStateStyle { get; set; }
public Style ColdStateStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TeaType teaType = (TeaType)value;
if (teaType == TeaType.Hot)
{
return HotStateStyle;
}
else if (teaType == TeaType.Cold)
{
return ColdStateStyle;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
It could be made more generic and re-usable.
You should also take a look at toggle buttons, they deal with this kind of thing internally.

How to automatically change text in a silverlight textbox?

I am using MVVM/Caliburn.Micro in a silverlight 5 project and I have a requirement to automatically change the text the user enters in a silverlight textbox to uppercase.
First, I thought I could just set the backing variable on the ViewModel to uppercase and the two way binding would change the text. That didn't work (though I believe it will if I use a lost focus event, but I cannot do that since I have other things I must do for KeyUp as well and attaching two events results in a xaml error)
Since that didn't work I tried calling a method on the KeyUp event. This technically works, but since it is replacing the text it puts the cursor back at the beginning, so the user ends up typing backwards.
This seems like fairly simple functionality - how do I transform the text a user types into uppercase? Am I missing something easy?
Here is my existing code. Xaml:
<TextBox x:Name="SomeName" cal:Message.Attach="[Event KeyUp] = [Action ConvertToUppercase($eventArgs)]" />
View Model:
public void ConvertToUppercase(System.Windows.Input.KeyEventArgs e)
{
SomeName = _someName.ToUpper();
//Other code that doesn't concern uppercase
}
EDIT FOR ALTERNATE SOLUTION:
McAden put forth a nice generic solution. I also realized at about the same time that there was an alternate solution (just pass the textbox as a param to the uppercase method and move the cursor), so here is the code for that as well:
xaml:
<TextBox x:Name="SomeName" cal:Message.Attach="[Event KeyUp] = [Action ConvertToUppercase($source, $eventArgs)]; [Event KeyDown] = [Action DoOtherStuffThatIsntQuestionSpecific($eventArgs)]" />
cs method:
public void ConvertToUppercase(TextBox textBox, System.Windows.Input.KeyEventArgs e)
{
//set our public property here again so the textbox sees the Caliburn.Micro INPC notification fired in the public setter
SomeName = _someName.ToUpper();
//move the cursor to the last so the user can keep typing
textBox.Select(SomeName.Length, 0);
}
and of course cs standard Caliburn.Micro property:
private String _someName = "";
public String SomeName
{
get
{
return _someName;
}
set
{
_someName = value;
NotifyOfPropertyChange(() => SomeName);
}
}
Create a ToUpper EventTrigger as mentioned here. Also create another one for whatever otherfunctionality you're trying to accomplish. Add them both in xaml:
<TextBox Text="{Binding SomeName, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<myBehaviors:UpperCaseAction/>
</i:EventTrigger>
<i:EventTrigger EventName="TextChanged">
<myBehaviors:MyOtherAction/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
EDIT: I've fully tested this solution using the following (NO code-behind is involved)
UpperCase Action:
public class UpperCaseAction : TriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
var selectionStart = AssociatedObject.SelectionStart;
var selectionLength = AssociatedObject.SelectionLength;
AssociatedObject.Text = AssociatedObject.Text.ToUpper();
AssociatedObject.SelectionStart = selectionStart;
AssociatedObject.SelectionLength = selectionLength;
}
}
Other Action:
public class OtherAction : TriggerAction<TextBox>
{
Random test = new Random();
protected override void Invoke(object parameter)
{
AssociatedObject.FontSize = test.Next(9, 13);
}
}
XAML namespaces (TestSL in this case being the namespace of my test project - use your namespace as appropriate):
xmlns:local="clr-namespace:TestSL"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
XAML TextBox
<Grid x:Name="LayoutRoot" Background="LightGray" Width="300" Height="200">
<TextBox TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="10" Width="100">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<local:UpperCaseAction />
</i:EventTrigger>
<i:EventTrigger EventName="TextChanged">
<local:OtherAction />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
UpperCaseConverter.cs:
namespace MyProject.Converters
{
/// <summary>
/// A upper case converter for string values.
/// </summary>
public class UpperCaseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertToUpper(value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return ConvertToUpper(value);
}
private string ConvertToUpper(object value)
{
if (value != null)
{
return value.ToString().ToUpper();
}
return null;
}
}
}
AppResources.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:conv="clr-namespace:MyProject.Converters;assembly=MyProject"
mc:Ignorable="d"
>
<!-- Converters -->
<conv:UpperCaseConverter x:Key="UpperCaseConverter"/>
</ResourceDictionary>
MyFormView.xaml:
<UserControl>
<TextBox Text="{Binding myText, Mode=TwoWay, Converter={StaticResource UpperCaseConverter}}" />
</UserControl>

Categories