WPF bind a dynamic generated slider to function - c#

First: Not a duplicate of Binding Button click to a method --- it's about button, and Relay command can't pass the arguments I need
Also, not a duplicate of How do you bind a Button Command to a member method? - it's a simple method with no arguments - nothing to do with my question.
Obviously (but just to make sure and avoid trolls) not a duplicate of this either Silverlight MVVM: where did my (object sender, RoutedEventArgs e) go?.
Now after clearing this (sorry, I am just really sick of being marked as "duplicate" by people who didn't understand my question), let's talk about the issue: :D
I am trying to bind a generated slider (using data template) to an event (value changed), I know it's impossible to bind an event and I must use ICommand, but I don't know how to get the event arguments to the command function, this is the xaml relevant code: (without the binding since it doesnt work)
<Slider Grid.Column="1" Grid.Row="1" Height="30" IsSnapToTickEnabled="True" Maximum="100" SmallChange="1" IsMoveToPointEnabled="True"/>
And this is the function I want it to be binded to:
public void vibrationSlider_move(object Sender, RoutedPropertyChangedEventArgs<double> e)
{
VibrationValue = (byte)e.NewValue;
SendPacket(cockpitType, (byte)Index.VibrationSlider, VibrationValue);
}
As you can see, I need to use the 'e' coming with the event, I have no idea how to reach it without using the "ValueChanged" slider event.
Notes:
Please don't tell me to add the "ValueChanged" attribute like this:
<Slider ValueChanged="VibrationSlider_move"/>
:)
It's a generated dynamic slider using DataTemplate with an observableCollection, the function isn't in the window.cs file, therefore just using an event is not possible.
Thank you.

You can use the MVVMLight Toolkit, which allows to send the EventArgs as CommandParameter to the ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand Command="{Binding ValueChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your command.Execute method, you now get an object as parameter which you just have to parse to the correct type...

You could create an extension
public partial class Extensions
{
public static readonly DependencyProperty ValueChangedCommandProperty = DependencyProperty.RegisterAttached("ValueChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Slider;
if (element != null)
{
element.ValueChanged -= OnSingleValueChanged;
if (e.NewValue != null)
{
element.ValueChanged += OnSingleValueChanged;
}
}
}));
public static ICommand GetValueChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(ValueChangedCommandProperty);
}
public static void SetValueChangedCommand(UIElement element, ICommand value)
{
element.SetValue(ValueChangedCommandProperty, value);
}
private static void OnSingleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var element = sender as Slider;
var command = element.GetValue(ValueChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element))
{
command.Execute(element);
e.Handled = true;
}
}
}
which then can be used in xaml as below.
<Slider Minimum="0" Maximum="100" local:Extensions.ValueChangedCommand="{Binding ValueChangedCommand}"/>

As #Philip W stated, you could use e.g. MVVMLight to help dealing with MVVM pattern and with your problem at hand.
You could, for example, have a XAML with DataTemplate and Slider like so:
<Window x:Class="WpfApplication1.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"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
Title="MainWindow"
Height="250"
Width="250">
<Window.Resources>
<DataTemplate x:Key="SomeTemplate">
<StackPanel Margin="15">
<!-- Wrong DataContext can drive you mad!1 -->
<StackPanel.DataContext>
<local:SomeTemplateViewModel />
</StackPanel.DataContext>
<TextBlock Text="This is some template"/>
<Slider
Height="30"
IsSnapToTickEnabled="True"
Maximum="100"
SmallChange="1"
IsMoveToPointEnabled="True">
<!-- Bind/pass event as command -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<command:EventToCommand
Command="{Binding Mode=OneWay, Path=ValueChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<!-- Show current value, just for sake of it... -->
<TextBlock
Text="{Binding Value}"
FontWeight="Bold"
FontSize="24">
</TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{StaticResource SomeTemplate}" />
</Window>
So basically you bind desired event to named Command and pass EventArgs to it as parameter. Then in your ViewModel, being the DataContext of you Slider, you handle the event-passed-as-command.
public class SomeTemplateViewModel : ViewModelBase
{
private double _value;
public SomeTemplateViewModel()
{
// Create command setting Value as Slider's NewValue
ValueChangedCommand = new RelayCommand<RoutedPropertyChangedEventArgs<double>>(
args => Value = args.NewValue);
}
public ICommand ValueChangedCommand { get; set; }
public double Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged(); } // Notify UI
}
}
This would give you something similar to this.

Since your slider is dynamically generated, nothing prevents you from adding your ValueChanged event at a later time:
XAML:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="10,143,0,0" VerticalAlignment="Top" Width="474" Grid.ColumnSpan="2" />
Code-behind:
public MainWindow()
{
InitializeComponent();
// it is a good idea to not allow designer to execute custom code
if (DesignerProperties.GetIsInDesignMode(this))
return;
slider.ValueChanged += Slider_ValueChanged;
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// do your stuff here
}
Checking design mode is not simple in any context, as pointed out here.

Related

Selection change/add items with ComboBox in WPF

Simple questions can be the hardest sometimes. 3 things I am trying to understand;
1. Allow a selection change within a combobox to help populate items in 2nd combobox.
2. Clear items in 2nd box before populating items.
3. Adding items in 2nd box.
Note that this code worked on my WinForms code, but I am trying to convert it to WPF and understand that code.
Code:
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" Margin="170,56,0,0" VerticalAlignment="Top" Width="160">
<ComboBoxItem Content="Hospital"/>
</ComboBox>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" Margin="30,131,0,0" VerticalAlignment="Top" Width="300"/>
$ComboBox_Location.add_SelectionChanged{
switch ($ComboBox_Location.SelectedItem){
"Hospital"{
$ComboBox_Printer.Items.Clear();
$Hospital = Get-Printer -ComputerName \\bmh01-print01 | where {($_.Name -like “*BMH01*”) -and ($_.DeviceType -eq "Print")}
foreach($Name in $Hospital){
$ComboBox_Printer.Items.Add("$($Name.name)");
}
}
}
Thank you in advance! And if any of you have a website or cite I could go to to see the specific coding for WPF, any help will be appreciated!
why is this question not answered by anyone.Anyway I will do my best to explain you. Hope I am not late to answer this question.
In WPF, we follow MVVM pattern, So there are 3 parts Model, View and ViewModel.
In viewmodel, we need to inherit Icommand and create a CommandHandler, so that if there is any button click / Selction changed will sent via this command and the delegated eventhandler will be raised.
The CommandHandler Class
public class CommandHandler : ICommand
{
private Action<object> _action;
private bool _canExeute;
public event EventHandler CanExecuteChanged;
private bool canExeute
{
set
{
_canExeute = value;
CanExecuteChanged(this, new EventArgs());
}
}
public CommandHandler(Action<object> action,bool canExecute)
{
_action = action;
_canExeute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExeute;
}
public void Execute(object parameter)
{
_action(parameter);
}
}
This CommandHandler will be used in the ViewModel Class, and then the viewmodel will be set as Datacontext to view via XAML.
public class ViewModelBase : INotifyPropertyChanged
{
private List<String> _printer = new List<string>();
private bool _canExecute;
public ViewModelBase()
{
_canExecute = true;
}
public List<string> Printers
{
get { return _printer; }
set { _printer = value; }
}
private ICommand _SelectedItemChangedCommand;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ICommand SelectedItemChangedCommand
{
get
{
return _SelectedItemChangedCommand ?? (_SelectedItemChangedCommand =
new CommandHandler(obj => SelectedItemChangedHandler(obj), _canExecute));
}
}
public void SelectedItemChangedHandler(object param)
{
var selectedItem = ((ComboBoxItem)param).Content;
switch (selectedItem)
{
case "Hospital":
Printers = new List<string>(); //clearing the list;
// Hospital = GetHospital();// - ComputerName \\bmh01 - print01 | where { ($_.Name - like “*BMH01 *”) -and($_.DeviceType - eq "Print")}
// Here I have added data hard coded, you need to call your method and assgin it to printers property.
Printers.Add("First Floor Printer");
Printers.Add("Second Floor Printer");
OnPropertyChanged(nameof(Printers));
break;
default:
Printers = new List<string>();
break;
}
}
}
The ViewModel class is also inheriting INotifyPropertyChanged, where we need to implement the event and raise it. Now we need to raise propertychanged event providing the property name which is changed using assignment. Therefore inside SelectionChangedCommand, we add Printer and then raise PropertyChanged Event by sending the Printers PropertyName in as Parameter.
The View, We can use either Window or UserControl, For this example I have used Window.
View:-
<Window x:Class="Combo2.MainScreen"
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"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:Combo2"
xmlns:ViewModel="clr-namespace:Combo2.Validate"
mc:Ignorable="d"
x:Name="Window1"
Title="MainScreen" Height="450" Width="800">
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Location" HorizontalAlignment="Left" Grid.Row="0" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="0" Grid.Column="1" >
<ComboBoxItem Content="Hospital"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Label Content="Printer Names" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Printers}" >
</ComboBox>
</Grid>
Now, As in winform we have click or selectionchanged event but in order to keep the designer separate from code, we are not directly coupling it with it. I mean to say write a selection changed event in code behind then we are not making justification to it. For more information click on https://www.tutorialspoint.com/mvvm/mvvm_introduction.htm which will give you more insight into MVVM.
Now if you notice, when there is a selection changed we have binded the Command Property to a Command Property present in the Viewmodel, which is possible using Interaction class.
So where did we link the view and viewmodel that it at the top of the xaml. Here the datacontext is bound to ViewmodelBase class(viewmodel)
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
Now answer to your question
1)Allow a selection change within a combobox to help populate items in 2nd combobox.
The selectionChanged event is called which will call the Command method present in the ViewModelBase and popluate the Printers Property.
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
Now since the viewmodel is bound to view any change to the property is displayed in the 2nd dropdown. Now that I have cleared and added data in Printers property, when the 1st drop is selected based on the text if matches "Hospital" the printers are added to the Property and displayed in 2nd Drop down.
2) Clear items in 2nd box before populating items
Before adding data in Printers property, it is cleared by instantiating the List, in your case it could be any other class. Now to whether the selected data is Hospital, we need to send the SelectedItem using the Command Parameter , we cast the "param" with ComboBoxItem and got the content.
3) Adding items in 2nd box.
We sure did add the values in Printers property.
Hope this helps you !!

Control property depending on other controls' properties? (Listener?)

I got used to implement event handlers to set a specific control's property, by creating a checking method and calling it in every handler, like that:
private void checkProperties()
{
myButton.IsEnabled = !String.IsNullOrWhiteSpace(myTextBox.Text) && myComboBox.SelectedIndex > -1;
}
private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
checkProperties();
}
private void myComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
checkProperties();
}
and
<Window x:Class="MyProgram.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">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Name="myTextBox" VerticalAlignment="Top" Width="120" TextChanged="myTextBox_TextChanged" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="0,38,0,0" Name="myComboBox" VerticalAlignment="Top" Width="120" SelectionChanged="myComboBox_SelectionChanged" />
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="45,84,0,0" Name="myButton" VerticalAlignment="Top" Width="75" IsEnabled="False" />
</Grid>
</Window>
But that gets pretty heavy and redundant when you have a property that depends on 10 or more other controls' properties (just think about a Wizard window and its "Next" button, which should only be enabled if every controls are valid).
Is there a way to modify a property to automatically change depending on other controls' properties?
I've read about Dependency Properties a bit, but I'm not sure I can, let's say, modify the "IsEnabled" property of myButton to meet my expectations.
You don't have to handle events. You should be using element binding, which automatically bind to target properties values. However, both properties in question should be a Dependancy property. It should work in your case.
See this example:
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname(v=vs.110).aspx
Consider using MVVM instead of code behind. Enabling a button is command logic that is testable, and could be handled very simply in the view model via an ICommand.CanExecute delegate. No binding would be required since WPF automatically calls CanExecute() when the UI changes.
class MyViewModel : INotifyPropertyChanged
{
public ICommand SomeCommand { get; private set; }
public string Text { get; set; } //INPC omited for brevity
public int SelectedIndex { get; set; } //INPC omited for brevity
public MyViewModel()
{
SomeCommand = new RelayCommand(DoSomeCommand, CanDoSomeCommand);
}
private void DoSomeCommand()
{
//Blah
}
private bool CanDoSomeCommand()
{
return !String.IsNullOrWhiteSpace(this.Text) && this.SelectedIndex > -1;
}
}
WPF Multibinding is probably what you need in similar situations - Multibinding WPF.
<Grid>
...
<Grid.Resources>
<somenamespace:ValidatorConverter x:Key="ValidatorConverterResource">
</Grid.Resources>
<TextBox Name="myTextBox" ... />
<ComboBox Name="myComboBox" ... />
<Button Name="myButton" ...>
<Button.IsEnabled>
<MultiBinding Converter="{StaticResource ValidatorConverterResource}"
ConverterParameter="Left">
<Binding Path="Text"
ElementName="myTextBox" />
<Binding Path="SelectedIndex"
ElementName="{myComboBoxSelf}" />
</MultiBinding>
</Button.IsEnabled>
</Button>
</Grid>
But it won't actually ease the problem (it will just move it from code-behind to converters):
public class ValidatorConverter: IConverter
{
public object Converter( ...) //...
public object ConverterBack( ...) //...
}
About the validation goal
There is possibly some easy way to do it, but I don't know it, so the easiest way to at least consolidate the issue to where it belongs is to use bindings and MVVM design pattern.
In the model(viewmodel) make some IsValid property(and bind it to the Next IsEnabled directly or even better - through the command) , that will be updated according to the current values of bound properties.
public class WizardViewModel : INotifyPropertyChanged
{
// OnPropertyChanged(String) and PropertyChanged event
public String Text
{
get //..
set
{
this._Text = value;
Validate();
this.OnPropertyChanged("SelectedItem")
}
}
public Object SelectedItem
{
get //..
set
{
this._SelectedItem = value;
Validate();
this.OnPropertyChanged("SelectedItem")
}
}
public bool IsValid { // ...}
private void Validate()
{
if (String.IsNullOrEmpty(this._Text))
this.IsValid = false;
// ....
}
}
And in xaml:
<somenamespace:WizardViewModel x:Key="WizardViewModelInstance">
// ....
<Grid.DataContext="{StaticResource WizardViewModelInstance}">
// ....
<TextBox ... Text="{Binding Text}"/>
<ComboBox ... SelectedItem="{Binding SelectedItem}"/>
<Button ... IsEnabled="{Binding IsEnabled}"/>
Such solution will decouple your logic from view and put everything related to such validation into one place.
Also:
If you want to learn about WPF validation you may want to read http://msdn.microsoft.com/en-us/library/ms753962(v=vs.110).aspx
If you want to learn about WPF commands, read http://msdn.microsoft.com/en-us/magazine/dn237302.aspx

Dynamic user control change - WPF

I'm developing an app in WPF and I need to change in runtime a content of a ContentControl depending than the user selected on ComboBox.
I have two UserControls and at my combo exists two itens, corresponding each one each.
First usercontrol:
<UserControl x:Class="Validator.RespView"
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"
mc:Ignorable="d"
d:DesignHeight="167" d:DesignWidth="366" Name="Resp">
<Grid>
<CheckBox Content="CheckBox" Height="16" HorizontalAlignment="Left" Margin="12,12,0,0" Name="checkBox1" VerticalAlignment="Top" />
<ListBox Height="112" HorizontalAlignment="Left" Margin="12,43,0,0" Name="listBox1" VerticalAlignment="Top" Width="168" />
<Calendar Height="170" HorizontalAlignment="Left" Margin="186,0,0,0" Name="calendar1" VerticalAlignment="Top" Width="180" />
</Grid>
Second usercontrol:
<UserControl x:Class="Validator.DownloadView"
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"
mc:Ignorable="d"
d:DesignHeight="76" d:DesignWidth="354" Name="Download">
<Grid>
<Label Content="States" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<ComboBox Height="23" HorizontalAlignment="Left" Margin="12,35,0,0" Name="comboBox1" VerticalAlignment="Top" Width="120" />
<RadioButton Content="Last 48 hs" Height="16" HorizontalAlignment="Left" Margin="230,42,0,0" Name="rdbLast48" VerticalAlignment="Top" />
<Label Content="Kind:" Height="28" HorizontalAlignment="Left" Margin="164,12,0,0" Name="label2" VerticalAlignment="Top" />
<RadioButton Content="General" Height="16" HorizontalAlignment="Left" Margin="165,42,0,0" Name="rdbGeral" VerticalAlignment="Top" />
</Grid>
At MainWindowView.xaml
<Window x:Class="Validator.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:du="clr-namespace:Validator.Download"
xmlns:resp="clr-namespace:Validator.Resp"
Title="Validator" Height="452" Width="668"
WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
<Window.Resources>
<DataTemplate DataType="{x:Type du:DownloadViewModel}">
<du:DownloadView/>
</DataTemplate>
<DataTemplate DataType="{x:Type resp:RespViewModel}">
<resp:RespView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Path=PagesName}"
SelectedValue="{Binding Path=CurrentPageName}"
HorizontalAlignment="Left" Margin="251,93,0,0"
Name="cmbType"
Width="187" VerticalAlignment="Top" Height="22"
SelectionChanged="cmbType_SelectionChanged_1" />
<ContentControl Content="{Binding CurrentPageViewModel}" Height="171" HorizontalAlignment="Left" Margin="251,121,0,0" Name="contentControl1" VerticalAlignment="Top" Width="383" />
</Grid>
</Window>
I assigned to the DataContext of the MainView, the viewmodel below:
public class MainWindowViewModel : ObservableObject
{
#region Fields
private ICommand _changePageCommand;
private ViewModelBase _currentPageViewModel;
private ObservableCollection<ViewModelBase> _pagesViewModel = new ObservableCollection<ViewModelBase>();
private readonly ObservableCollection<string> _pagesName = new ObservableCollection<string>();
private string _currentPageName = "";
#endregion
public MainWindowViewModel()
{
this.LoadUserControls();
_pagesName.Add("Download");
_pagesName.Add("Resp");
}
private void LoadUserControls()
{
Type type = this.GetType();
Assembly assembly = type.Assembly;
UserControl reso = (UserControl)assembly.CreateInstance("Validator.RespView");
UserControl download = (UserControl)assembly.CreateInstance("Validator.DownloadView");
_pagesViewModel.Add(new DownloadViewModel());
_pagesViewModel.Add(new RespViewModel());
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return _changePageCommand;
}
}
public ObservableCollection<string> PagesName
{
get { return _pagesName; }
}
public string CurrentPageName
{
get
{
return _currentPageName;
}
set
{
if (_currentPageName != value)
{
_currentPageName = value;
OnPropertyChanged("CurrentPageName");
}
}
}
public ViewModelBase CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
int indexCurrentView = _pagesViewModel.IndexOf(CurrentPageViewModel);
indexCurrentView = (indexCurrentView == (_pagesViewModel.Count - 1)) ? 0 : indexCurrentView + 1;
CurrentPageViewModel = _pagesViewModel[indexCurrentView];
}
#endregion
}
On MainWindowView.xaml.cs, I wrote this event to do the effective change:
private void cmbType_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
{
MainWindowViewModel element = this.DataContext as MainWindowViewModel;
if (element != null)
{
ICommand command = element.ChangePageCommand;
command.Execute(null);
}
}
The app run ok and I inspected the application with WPFInspector and saw that the view changes when the combobox is changed internally, but the ContentControl still empty visually..
Sorry about the amount of code that I posted and my miss of knowledge but I'm working with this a long time and can't solve this problem.
Thanks
Issues:
Firstly don't ever create View related stuff in the ViewModel (UserControl). This is no longer MVVM when you do that.
Derive ViewModels from ViewModelBase and not ObservableObject unless you have a compelling reason to not use ViewModelBase when using MVVMLight. Keep ObservableObject inheritence for Models. Serves as a nice separation between VM's and M's
Next you do not need to make everything an ObservableCollection<T> like your _pagesViewModel. You do not have that bound to anything in your View's so it's just a waste. Just keep that as a private List or array. Check what a type actually does in difference to a similar other one.
Not sure about this one, maybe you pulled this code snippet as a demo, but do not use margins to separate items in a Grid. Your Layout is essentially just 1 Grid cell and the margins have the items not overlap. If you're not aware of that issue, Check into WPF Layout Articles.
Please don't forget principles of OOP, Encapsulation and sorts when writing a UI app. When having Properties like CurrentPageViewModel which you don't intend the View to switch make the property setter private to enforce that.
Don't resort to code-behind in the View too soon. Firstly check if it's only a View related concern before doing so. Am talking about your ComboBox SelectionChanged event handler. Your purpose of that in this demo is to switch the Bound ViewModel which is held in the VM. Hence it's not something that the View is solely responsible for. Thus look for a VM involved approach.
Solution:
You can get a working example of your code with the fixes for above from Here and try it out yourself.
Points 1 -> 5 are just basic straightforward changes.
For 6, I've created a SelectedVMIndex property in the MainViewModel which is bound to the SelectedIndex of the ComboBox. Thus when the selected index flips, the property setter after updating itself updates the CurrentPageViewModel as well such as
public int SelectedVMIndex {
get {
return _selectedVMIndex;
}
set {
if (_selectedVMIndex == value) {
return;
}
_selectedVMIndex = value;
RaisePropertyChanged(() => SelectedVMIndex);
CurrentPageViewModel = _pagesViewModel[_selectedVMIndex];
}
}

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>

WPF - Set Focus when a button is clicked - No Code Behind

Is there a way to set Focus from one control to another using WPF Triggers?
Like the following example:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Name="txtName"></TextBox>
<TextBox Grid.Row="1" Name="txtAddress"></TextBox>
<Button Grid.Row="2" Content="Finish">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<!-- Insert cool code here-->
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
</Page>
Is there a way for this EventTrigger to put to focus on the textBox "txtName"?
I am trying to find the way to do something like this using strict MVVM. If this is something that should not be done via the XAML (in MVVM) then that is fine. But I would like to see some kind of documentation as to how it fit in the MVVM pattern doing it outside the XAML.
Have you considered using an attached behaviour. They are simple to implement and use AttachedProperty's. Although it still requires code, this code is abstracted away in a class and be reused. They can eliminate the need 'code behind' and are often used with the MVVM pattern.
Try this one and see if it works for you.
public class EventFocusAttachment
{
public static Control GetElementToFocus(Button button)
{
return (Control)button.GetValue(ElementToFocusProperty);
}
public static void SetElementToFocus(Button button, Control value)
{
button.SetValue(ElementToFocusProperty, value);
}
public static readonly DependencyProperty ElementToFocusProperty =
DependencyProperty.RegisterAttached("ElementToFocus", typeof(Control),
typeof(EventFocusAttachment), new UIPropertyMetadata(null, ElementToFocusPropertyChanged));
public static void ElementToFocusPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var button = sender as Button;
if (button != null)
{
button.Click += (s, args) =>
{
Control control = GetElementToFocus(button);
if (control != null)
{
control.Focus();
}
};
}
}
}
And then in your XAML do something like...
<Button
Content="Click Me!"
local:EventFocusAttachment.ElementToFocus="{Binding ElementName=textBox}"
/>
<TextBox x:Name="textBox" />
I'm not near visual studio so I can't actually try this right now, but off the top of my head, you should be able to do something like this:
FocusManager.FocusedElement="{Binding ElementName=txtName}">
Edit:
There is a followup question (asked more recently) about this here: How to set autofocus only in xaml? which contains this method, and a few different ideas on how to use it.
You could also use a WPF Behavior...
public class FocusElementAfterClickBehavior : Behavior<ButtonBase>
{
private ButtonBase _AssociatedButton;
protected override void OnAttached()
{
_AssociatedButton = AssociatedObject;
_AssociatedButton.Click += AssociatedButtonClick;
}
protected override void OnDetaching()
{
_AssociatedButton.Click -= AssociatedButtonClick;
}
void AssociatedButtonClick(object sender, RoutedEventArgs e)
{
Keyboard.Focus(FocusElement);
}
public Control FocusElement
{
get { return (Control)GetValue(FocusElementProperty); }
set { SetValue(FocusElementProperty, value); }
}
// Using a DependencyProperty as the backing store for FocusElement. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FocusElementProperty =
DependencyProperty.Register("FocusElement", typeof(Control), typeof(FocusElementAfterClickBehavior), new UIPropertyMetadata());
}
Here is the XAML to use the behavior.
Include namespaces:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApplication1"
Attach WPF Behavior to button and bind element you want to set focus to:
<Button Content="Focus" Width="75">
<i:Interaction.Behaviors>
<local:FocusElementAfterClickBehavior FocusElement="{Binding ElementName=CheckBoxComboBox, Mode=OneWay}"/>
</i:Interaction.Behaviors>
</Button>
<ComboBox x:Name="CheckBoxComboBox" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Grid.Row="1"/>
So this way you have no code behind and it is reusable on any control that inherits from ButtonBase.
Hope this helps someone.
you need a TriggerAction to invoke the Focus() method on the desired control.
public class SetFocusTrigger : TargetedTriggerAction<Control>
{
protected override void Invoke(object parameter)
{
if (Target == null) return;
Target.Focus();
}
}
To have the focus set to a Control , you place a Triggers collection after your LayoutRoot (or any control really), select the event as the trigger, and select the SetFocusTrigger as the class to run. In the SetFocusTrigger declaration, you put the name of the control that you want to receive the focus by using the TargetName property.
<Button x:Name="LayoutRoot" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Clicked">
<local:SetFocusTrigger TargetName="StartHere"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox x:Name="StartHere"/>
</Button>
Is this what you want?
<TextBox Name="txtName"></TextBox>
<TextBox Grid.Row="1" Name="txtAddress"></TextBox>
<Button Grid.Row="2" Content="Finish">
<Button.Style>
<Style TargetType="{x:Type Button}">
<EventSetter Event="Click" Handler="MoveFocusOnClick" />
</Style>
</Button.Style>
<!--<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
</EventTrigger>
</Button.Triggers>-->
</Button>
c#:
public void MoveFocusOnClick(object sender, RoutedEventArgs e)
{
Keyboard.Focus(txtName); // Or your own logic
}
This is the same as Ian Oakes' solution, but I made a couple minor changes.
The button type can be more general, namely ButtonBase, to handle more cases, such as ToggleButton.
The target type can also be more general, namely UIElement. Technically, this could be IInputElement, I suppose.
Made the event handler static so that it won't generate a runtime closure every time this is used.
[edit: 2019] Updated to use null-conditional and C#7 expression body syntax.
Many thanks to Ian.
public sealed class EventFocusAttachment
{
public static UIElement GetTarget(ButtonBase b) => (UIElement)b.GetValue(TargetProperty);
public static void SetTarget(ButtonBase b, UIElement tgt) => b.SetValue(TargetProperty, tgt);
public static readonly DependencyProperty TargetProperty = DependencyProperty.RegisterAttached(
"Target",
typeof(UIElement),
typeof(EventFocusAttachment),
new UIPropertyMetadata(null, (b, _) => (b as ButtonBase)?.AddHandler(
ButtonBase.ClickEvent,
new RoutedEventHandler((bb, __) => GetTarget((ButtonBase)bb)?.Focus()))));
};
Usage is basically the same as above:
<ToggleButton z:EventFocusAttachment.Target="{Binding RelativeSource={RelativeSource Self}}" />
Note that the event can target/focus the originating button itself.
Look if you're using any Dispatcher then it would be helpful but this is a short trick I used in my code. Just use the Loaded event in your XAML and make a new handler. In that handler paste this code and bingo! you're ready to go
Your loaded event with some arguments here...
{
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Normal, new System.Action(() => {
The element to be focused goes here......
}));
}
PS: It requires Windows. Threading if you didn't know ;)

Categories