The textbox is bound to a ViewModel and if its content has been updated the ViewModel would submit the update to a source(normally db).
The XAML side should be like this.
<TextBox Grid.Column="1" Grid.Row="1"
Text="{Binding Path=LowVoltage, StringFormat={}{0:N3}, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"/>
Meanwhile, in the ViewModel, is it a good choice to do update like this?
private float lowVoltage;
public float LowVoltage
{
get { return this.lowVoltage; }
set
{
if (this.lowVoltage != value)
{
this.lowVoltage = value;
**//dbContext.Submit(); --here**
this.RaisePropertyChanged("LowVoltage");
}
}
}
I wouldn't choose to make database operations inside a property setter, better work with Commands, like access the ViewModel in the behind code LostFocus event handler and execute it or use EventTrigger in XAML.
<Grid xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SomeEvent">
<i:InvokeCommandAction Command="{Binding Path=SomeCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
Related
I am a new in C# and MVVM approach, I created such TextBox
<TextBox x:Name="Tb_fps"
Grid.Column="0"
Text="{Binding FPS, UpdateSourceTrigger=PropertyChanged}"
x:FieldModifier="private"
Margin="4,4,0,4"
HorizontalAlignment="Stretch"/>
and as you can see I am using UpdateSourceTrigger=PropertyChanged, but additionally I would like to use LostFocus and GotFocus events...
But I can't with this implementation of DataBinding...
Question is - how to handle all this events on one TextBox view?
EDIT
Associated with #Joe H answer
You need to use an EventTrigger from the
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
namespace.
This allows the conversion of an event into a ICommand pattern. Also you don't need the EventSource as that is the default for triggering a binding. This would be the Xaml and the ViewModel code follows
<Grid>
<StackPanel>
<TextBox Text="{Binding Text}" Height="40" Width="80">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding LostFocusCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<TextBox Text="Need something to change focus to" Height="40" Width="80" />
</StackPanel>
</Grid>
ViewModel:
public class MyWindowViewModel : INotifyPropertyChanged
{
private string _text;
public string Text
{
get => _text;
set
{
_text = value;
OnPropertyChanged();
}
}
public ICommand LostFocusCommand => new ActionCommand(ExecuteLostFocus);
private void ExecuteLostFocus()
{
Console.WriteLine("LostFocus");
}
public ICommand GotFocusCommand => new ActionCommand(ExecuteGotFocus);
private void ExecuteGotFocus()
{
Console.WriteLine("GotFocus");
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Also, you should be aware that the Text property in the ViewModel does not update until the TextBox loses focus. If you want to capture text as it changes a TextChanged handler is needed
You can bind to any event from XAML using the Microsoft.Xaml.Behaviors.Wpf package.
After installing the package, you must include the behaviors namespace (xmlns:i="http://schemas.microsoft.com/xaml/behaviors) to your XAML file. Then, you can use the <i:InteractionTrigger> markup in your control (which is a XAML behaviour).
This markup allows you to declare an EventTrigger, which works a bit like style triggers, if your familiar with this. You can specify the control event you want to hook up to, and then declare a TriggerAction markup to react to the event.
You will most likely want to use a TriggerAction of type InvokeCommandAction, that will bind to a command in your viewmodel.
Your code would look like this:
<TextBox
x:Name="Tb_fps"
Grid.Column="0"
Margin="4,4,0,4"
HorizontalAlignment="Stretch"
x:FieldModifier="private"
Text="{Binding FPS, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding LostFocusCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding GotFocusCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Edit: I changed System.Windows.Interactivity package to the Microsoft.Xaml.Behaviors.Wpf, which is a more recent and maintained package.
I am creating my audio player on WPF using NAudio.
I am adding a slider that will be scroll the song.
XAML:
<Slider Height="30"
Value="{Binding Path=MediaReader.Position, Mode=TwoWay}"
Maximum="{Binding Path=MediaReader.Length, Mode=OneWay}"/>
Note. MediaReader - it's a property that returns the object of type MediaFoundationReader:
MediaFoundationReader mediaReader;
public MediaFoundationReader MediaReader => mediaReader;
Problem: while the song is plaing the slider property Value doesn't change! But by scrolling the thumb of the slider the property Position of the MediaReader changes.
Why does it work so and how can I solve that?
Just take a look to this tutorial:
https://www.pluralsight.com/guides/building-a-wpf-media-player-using-naudio
The author uses also a slider control and binds the current track position, e.g.:
<Slider Grid.Column="0" Minimum="0" Maximum="{Binding CurrentTrackLenght, Mode=OneWay}" Value="{Binding CurrentTrackPosition, Mode=TwoWay}" x:Name="SeekbarControl" VerticalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseDown">
<i:InvokeCommandAction Command="{Binding TrackControlMouseDownCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseUp">
<i:InvokeCommandAction Command="{Binding TrackControlMouseUpCommand}"></i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
public double CurrentTrackPosition
{
get { return _currentTrackPosition; }
set
{
if (value.Equals(_currentTrackPosition)) return;
_currentTrackPosition = value;
OnPropertyChanged(nameof(CurrentTrackPosition));
}
}
I think you can get the idea behind this...
I have a DataGrid with a column of checkboxes and I have a checkbox in the DataGrid header that, when checked, checks all the checkboxes. Based on this answer, I have a command bound to the "checked" event and another one that binds to the "unchecked" event.
All the relevant files are below (simplified, of course)
My XAML:
<DataGridTemplateColumn Width="40">
<DataGridTemplateColumn.Header>
<CheckBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding CheckAllRowsCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding UncheckAllRowsCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
My xaml.cs
public partial class MyTableView: UserControl
{
public MyTableView()
{
InitializeComponent();
DataContext = new MyTableViewModel();
}
}
MyTableViewModel.cs
public class MyTableViewModel: BaseViewModel
{
public MyTableViewModel() : base()
{
CheckAllRowsCommand= new CheckAllRowsCommand(this);
UncheckAllRowsCommand = new UncheckAllRowsCommand(this);
}
public ICommand CheckAllRowsCommand{ get; }
public ICommand UncheckAllRowsCommand{ get; }
}
CheckAllRowsCommand
public class CheckAllRowsCommand: BaseCommand
{
public CheckAllRowsCommand(MyTableViewModel parent) : base(parent)
{
}
public override bool CanExecute(object parameter)
{
return true;
}
public override void Execute(object parameter)
{
// Set the Selected property of each data row
}
}
When running this, I get the following error:
System.Windows.Data Error: 40 : BindingExpression path error:
'CheckAllRowsCommand' property not found on 'object' ''CheckBox'
(Name='')'. BindingExpression:Path=CheckAllRowsCommand;
DataItem='CheckBox' (Name=''); target element is 'InvokeCommandAction'
(HashCode=47015983); target property is 'Command' (type 'ICommand')
Any help would be greatly appreciated.
CheckAllRowsCommand is a property in a ViewModel, not in CheckBox. Try to bind to viewModel via DataGrid (it should have inherited the DataContext):
<DataGrid Name="NameOfDataGrid" ...>
<i:InvokeCommandAction Command="{Binding DataContext.CheckAllRowsCommand, ElementName=NameOfDataGrid}"/>
This is a more verbose solution than ASh has posted but is more flexible, in my opinion.
<CheckBox DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}">
...
</CheckBox>
The problem is that the binding in the template seems to be using the CheckBox as it's data context when you would think it would use the data context of the containing control. WPF can be funky sometimes, an any time I see an error like this (ie BindingExpression path error: 'blah' property not found on 'object' ''blah') I always assume the data context isn't being inferred correctly.
The workaround I'm proposing is to forcibly set the data context on the CheckBox using a relative source. Relative sources can be used to declaratively say "use the first parent of this type" for example. This binding is saying "set my DataContext property to the DataContext of the first parent above me of type DataGrid". Logically the first parent of that checkbox of type DataGrid is going to be the one you intended. Then your inner command bindings don't need to change and everything should work as expected.
You could bind the DataContext of the CheckBox to the DataContext of the parent UserControl where the command properties are defined using a {RelativeSource}:
<CheckBox DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding CheckAllRowsCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding UncheckAllRowsCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
I am working on wizard kind of application, where I am maintaining the wizards in
a list named List<ViewmodelsForWizard>.
I have used same UserControl UCView to render two different pages by simply tweaking the ViewModel values stored in List<ViewmodelsForWizard>.
The problem is that SelectionChangedCommand for previous page gets fired on load of the next page. (Both pages uses same UserControl UCView and ViewModel)
MainWindow
<Grid DataContext="{Binding currentWizard}">
<Grid.Resources>
<DataTemplate DataType="{x:Type viewmodel}">
<local:UCView DataContext="{Binding}"/>
</DataTemplate>
</Grid.Resources>
<ContentControl Content="{Binding}" />
</Grid>
I have following ViewModel:
//all other properties and commands
private ICommand selectionChangedCommand;
public ICommand SelectionChangedCommand
{
get
{
if (selectionChangedCommand == null)
selectionChangedCommand = new DelegateCommand(OnSelectionChanged());
return selectionChangedCommand;
}
set
{
selectionChangedCommand = value;
}
}
//all other properties and commands
Selectionchanged in UCView
<ComboBox ItemsSource="{Binding items}"
DisplayMemberPath="data"
SelectedValue="{Binding selected}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectionChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
Any help would be great. Thanks.
In my xaml file with DataTemplates I want to add a DataContextChanged event to my ListBox that is in one of the templates, so i do this:
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
DataContextChanged="MyListBox_DataContextChanged"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}"/>
</DataTemplate>
But in which file to implement "MyListBox_DataContextChanged"?
when working with Mvvm you don't handle events directly like you do when working with code behind, in your case the command that handles the DataContextChanged should be implemented in the corresponding ViewModel of the page where this DataTemplate is used,
and finally using a simple hack you can execute the associated Command when the DataContextChanged Event accured, your code should looks like so :
The Xaml :
<DataTemplate x:Key="MyTemplate">
<ListBox Background="Transparent"
DataContext="{Binding Source={StaticResource Locator}}"
SelectedItem="{Binding MyViewModel.SelSegment, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="DataContextChanged">
<command:EventToCommand Command="{Binding Mode=OneWay,Path=MyViewModel.MyListBox_DataContextChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</DataTemplate>
and Add the following command to your ViewModel :
private RelayCommand __myListBox_DataContextChangedCommand;
public RelayCommand MyListBox_DataContextChangedCommand
{
get
{
return __myListBox_DataContextChangedCommand
?? (__myListBox_DataContextChangedCommand = new RelayCommand(
() =>
{
//Your Event's Handler Goes Here
}));
}
}
Edit :
You could read more about EventToCommand at
Commands, RelayCommands and EventToCommand