I have a CheckBox that's set up like so:
<CheckBox x:Name="ViewTypeCheckbox" IsChecked="{Binding ViewType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction Command="{Binding Refresh}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
This functions as it's supposed to. When checked or unchecked by mouse click, the command is fired in the ViewModel.
You see the checkBox is databound to a bool property ("ViewType") that regularly turns from true to false and viseversa in response to user input.
The problem is I need the EventTrigger to fire when checked or unchecked by the ViewModel.
I've tried changing the "EventName" to "Checked", "IsChecked" and "UnChecked" but that doesn't seem to do anything.
Is there any additional code I need to implement? How would I get this to work?
You don't need Interaction.Triggers,
WPF provides support for commands out of the box.
Try simplifying your XAML by using a command attribute instead. It should resolve your issue.
If you need to call some code when the ViewTypeCheckbox.IsChecked changes its value, you can simply register an event handler to the PropertyChanged event of your view model (assuming it implements INotifyPropertyChanged), then call the code when the property ViewType changes:
myViewModel.PropertyChanged += new PropertyChangedEventHandler(onPropertyChanged);
...
private void onPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName=="ViewType")
{
// Do your stuff here
// Ex. fire the Refresh Command
}
}
This can be done in the ViewModel class itself or whatever you need.
To complete the example I add a short implementation of INotifyPropertyChanged in the viewmodel class:
public class MyViewModel : INotifyPropertyChanged
{
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler!=null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
...
// property definition to bind to ViewTypeCheckbox.IsChecked
private bool _viewType;
public bool ViewType
{
get { return _viewType; }
set
{
if (value != _viewType)
{
_viewType = value;
RaisePropertyChanged("ViewType");
}
}
}
}
Hope it helps
You don't need a trigger to execute your action. You can do it in the viewmodel when the property change.
public Boolean ViewType
{
get
{
return this.something;
}
set
{
this.something = value;
if (true == this.something)
{
this.Refresh();
}
}
}
Related
I'm trying to hide an element based on a bool. I use a button in this example, but it doesn't work no matter what element type I use.
Here is the XAMl that contains the binding.
<Button
Command="local:CustomCommands.Toggle"
Content="Toggle"
Visibility="{Binding Show, Converter={StaticResource BoolToVis}}" />
Here is the view model I am binding to.
public class MyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool show = true;
public bool Show
{
get
{
return show;
}
set
{
value = show;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Show)));
}
}
}
}
I've debugged and see the property changing, but nothign updates on the view.
Any ideas?
I have a text box:
<TextBox Height="20" Width="150" Text="{Binding MyProperty,NotifyOnValidationError=True,ValidatesOnDataErrors=True}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Validation.Error">
<mvvm:EventToCommand Command="{Binding MyCmd}" PassEventArgsToCommand="True" ></mvvm:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
My ViewModel looks like this:
public class MyViewModel : ValidationViewModelBase, INotifyPropertyChanged
{
private int myVar;
[Range(0, 10)]
public int MyProperty
{
get { return myVar; }
set
{
myVar = value;
OnPropertyChanged("MyProperty");
}
}
public MyViewModel()
{
MyCmd = new RelayCommand<RoutedEventArgs>(Valid);
}
public RelayCommand<RoutedEventArgs> MyCmd { get; set; }
private void Valid(RoutedEventArgs args)
{
//Do something
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
#endregion INotifyPropertyChanged
}
When I catch the event Validation.Error in Code Behind it works:
But when I try to run it this way with the Event Command is not coming Valid function.
Did I miss something?
Since Validation.Error is Attached Event, then it does not work with EventToCommand normally.
The answer you will find at the link below:
EventToCommand with attached event
There is no Validation.Error event for TextBox. Furthermore, there is no Validating event for System.Controls.TextBox (which you are using).
Use LostFocus to validate the textbox or see this question if you want to use Validation with MVVM pattern
My project is based on the MVVM pattern.
I have built a tree view that shows my file system.
Each folder has a checkbox for selecting the current folder.
The selection process is taking some time so, while the operation runs, there is a button which is disabled and at the end of the operation I`m enabling the button.
My problem is that when the button gets "disabled" I see it immediately. However, when the button is going back to the enabled mode I must do some action (like mouse click) to see the button enabled.
How can I make sure that the UI will be updated immediately after the button is enabled?
These are my buttons:
<Button Content="<- Back" Margin="5,0,5,0" Width="80" Height="25"
IsEnabled="{Binding CanMoveToPreviousPage, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=NavigateBackCommand, IsAsync=True}" />
<Button Content="{Binding ButtonNextCaption}" Margin="5,0,5,0" Width="80" Height="25"
IsEnabled="{Binding CanMoveToNextPage, UpdateSourceTrigger=PropertyChanged}"
Command="{Binding Path=NavigateNextCommand, IsAsync=True}" />
In my ViewModel I added this code:
public bool CanMoveToNextPage
{
get
{
return this.CurrentPage != null && this.CurrentPage.CanMoveNext;
}
set
{
if (CurrentPage != null)
{
this.CurrentPage.CanMoveNext = value;
OnPropertyChanged("CanMoveToNextPage");
}
}
}
public bool CanMoveToPreviousPage
{
get { return 0 < this.CurrentPageIndex && CurrentPage.CanMoveBack; }
set
{
if (CurrentPage != null)
{
this.CurrentPage.CanMoveBack = value;
OnPropertyChanged("CanMoveToPreviousPage");
}
}
}
The UI update happens just after I execute a mouse click or any keystroke.
This is the code of the action that is disabling and enabling the buttons:
void bg_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
DecrementDoneCounter();
if (ThreadSafeCouner == 0)//means all bg workers are done
{
UIlimitation(true);
}
}
private int ThreadSafeCouner; // check how many bgworkers run
public void IncrementDoneCounter() { Interlocked.Increment(ref ThreadSafeCouner); }
public void DecrementDoneCounter() { Interlocked.Decrement(ref ThreadSafeCouner); }
void bg_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
IncrementDoneCounter();
UIlimitation(false);
((bgArguments)e.Argument).SelectedDirectory.CanSelected = false;
MarkItems(((bgArguments)e.Argument).SelectedDirectory, ((bgArguments)e.Argument).IsSelect);
((bgArguments)e.Argument).FreeWorkerAllocation();
((bgArguments)e.Argument).SelectedDirectory.CanSelected = true;
}
//this is the enabling action which execute the propeties setters at the upper part of this post
private static void UIlimitation(bool limit)
{
MainWindowViewModel.Instance.CanMoveToNextPage = limit;
MainWindowViewModel.Instance.CanMoveToPreviousPage = limit;
}
What can I do?
You can adjust on your control Binding mode TwoWay and define triggers with PropertyChanged
{Binding ElementName=.., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
OK I found a solution.
I tried everything without success and eventually I found this thread:
Refresh WPF Command
I have used CommandManager.InvalidateRequerySuggested()
And its works.
Thanks for your help
Here's a code example of how you might set up your ViewModel with the INotifyPropertyChanged method of sending messages to update the UI:
public class MyViewModel : INotifyPropertyChanged
{
/******************************************************/
/* Property that you have created two-way binding for */
/******************************************************/
private double _myProperty
public double MyProperty
{
get { return _myProperty; }
set
{
_myProperty = value;
OnNotifyPropertyChanged("MyProperty");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged Members
}
I'm using Galasoft's Light MVVM for my Siverlight project.
I have setup everything as instructed: the ViewModel is bound to View's DataContext;
I have a canvas named inkCanvas in the View.
When the ViewModel gets the updated project data, I need to reference inkCanvas to create a CanvasRender instance public CanvasRender(Canvas canvas, ProjectData pdata).
The problem is in MVVM, the ViewModel knows nothing about View, so how can I reference a control (inkCanvas) in View?
P.S. (Edited): The workaround I made is: when I pass the project data to the ViewModel, I also pass the inkCanvas from View's code-behind. hmmm, now my code-behind is not clean.
Per the comments above, one way to do this is to extend Canvas and keep the reference to CanvasRender inside that class.
public class MyCanvas : Canvas
{
private CanvasRender _canvasRender;
private ProjectData _data;
public ProjectData Data
{
get { return _data; }
set
{
_data = value;
_canvasRender = new CanvasRender(this, _data);
}
}
public MyCanvas() : base()
{
}
}
You'd probably want to also make ProjectData a Dependency Property so that it's bindable.
This allows you to maintain the MVVM pattern, because now you can write in XAML:
<local:MyCanvas ProjectData="{Binding ViewModel.ProjectData}" />
In MVVM Pattern, you won't reference a Control directly in ViewModel. In MVVM, all is "binding". You inkCanvas will be binding to a property in your ViewModel.
public class MyViewModel : INotifyPropertyChanged
{
private readonly StrokeCollection _mystrokes;
public MyViewModel ()
{
_mystrokes= new StrokeCollection();
(_mystrokesas INotifyCollectionChanged).CollectionChanged += delegate
{
//the strokes have changed
};
}
public event PropertyChangedEventHandler PropertyChanged;
public StrokeCollection MyStrokes
{
get
{
return _mystrokes;
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And XAML:
<InkCanvas Strokes="{Binding MyStrokes}"/>
Edit :
Maybe the workaround for your case is to use EventToCommand : this allow tobind an UI event to an ICommand directly in XAML ( and use Args to pass a ref to the inkCancas)
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding Mode=OneWay, Path=LoadedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
If your going to use the EventToCommand approach (which you tried in another answer), then instead of using the PassEventArgsToCommand property use the CommandParameter property and bind it to your Canvas.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<cmd:EventToCommand Command="{Binding Path=CanvasLoadedCommand}"
CommandParameter="{Binding ElementName=inkCanvas}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Then in your ViewModel:
public class ViewModel
{
private Canvas m_canvas;
public RelayCommand<Canvas> CanvasLoadedCommand { get; private set; }
public ViewModel()
{
CanvasLoadedCommand = new RelayCommand<Canvas>(canvas =>
{
m_canvas = canvas;
});
}
}
So as soon as your canvas is loaded, you should then have a reference to it saved in your view model.
I have simple issue setting a two-way databinding of a checkbox in Silverlight 3.0. It must be a no-brainer but probably I forgot my brain home today...
I defined a Model class to represent my .. 'data'. I implemented the INotifyPropertyChanged interface to enable the UI to see when the data changes.
public class Model : INotifyPropertyChanged
{
private bool _value;
public bool Value
{
get { return this._value; }
set
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
this._value = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Next I put a checkbox and a button on the .. 'form' :
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="check" IsChecked="{Binding Value, Mode=TwoWay}" Content="SomeLabel"/>
<Button Click="Button_Click" Content="Test" />
</StackPanel>
Then I supplied the data in the constructor :
public MainPage()
{
InitializeComponent();
this.DataContext = new Model() { Value = true };
}
The issue is that you have to click twice on the checkbox for it to check/uncheck unless I de-implement the INotifyPropertyChanged. If de-implement it however, then the UI doesn't notice if I change the underlying data.
If I remove the Mode=TwoWay bit from the IsChecked binding expression then also the UI won't notice the underlying data change even if the Model is implementing the interface.
How can I do to :
Have the checkbox bound to the data at startup
Have the checkbox IsChecked change to modify the underlying data
Have the checkbox detect the underlying data change and update itself?
You've got a sequencing error in your set property procedure, you need to assign to _value before notifying the change :-
set
{
this._value = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}