How can I get Caliburn.Micro to map a key gesture to an action method on my ViewModel?
For example, I want to implement a tabbed interface, and I want my ShellViewModel to have a NewTab method, which the user should to be able to invoke by pressing Ctrl+T on the keyboard.
I know that the full Caliburn framework has support for gestures, but how can I do this using Caliburn.Micro? Is there perhaps some way to bind an action to a RoutedCommand (since RoutedCommands already support input gestures)? Or some other way to get gesture support?
I modified example to enable support for global key-bindings.
You just need to add the folowing code to your view:
<i:Interaction.Triggers>
<common:InputBindingTrigger>
<common:InputBindingTrigger.InputBinding>
<KeyBinding Modifiers="Control" Key="D"/>
</common:InputBindingTrigger.InputBinding>
<cl:ActionMessage MethodName="DoTheMagic"/>
</common:InputBindingTrigger>
</i:Interaction.Triggers>
And whenever Ctr+D is pressed the method DoTheMagic will be exexuted. Here is the modified InputBindingTrigger code:
public class InputBindingTrigger : TriggerBase<FrameworkElement>, ICommand
{
public static readonly DependencyProperty InputBindingProperty =
DependencyProperty.Register("InputBinding", typeof (InputBinding)
, typeof (InputBindingTrigger)
, new UIPropertyMetadata(null));
public InputBinding InputBinding
{
get { return (InputBinding) GetValue(InputBindingProperty); }
set { SetValue(InputBindingProperty, value); }
}
public event EventHandler CanExecuteChanged = delegate { };
public bool CanExecute(object parameter)
{
// action is anyway blocked by Caliburn at the invoke level
return true;
}
public void Execute(object parameter)
{
InvokeActions(parameter);
}
protected override void OnAttached()
{
if (InputBinding != null)
{
InputBinding.Command = this;
AssociatedObject.Loaded += delegate {
var window = GetWindow(AssociatedObject);
window.InputBindings.Add(InputBinding);
};
}
base.OnAttached();
}
private Window GetWindow(FrameworkElement frameworkElement)
{
if (frameworkElement is Window)
return frameworkElement as Window;
var parent = frameworkElement.Parent as FrameworkElement;
Debug.Assert(parent != null);
return GetWindow(parent);
}
}
Caliburn.Micro's Actions mechanism is built on top of System.Windows.Interactivity. So, you can create a custom trigger based on TriggerBase to do whatever you want, including global keyboard gestures. Then, just plug the ActionMessage into your trigger and viola!
Inherit from Caliburn's ActionMessage (which is a TriggerAction) and attach the derived trigger to the KeyDown event in XAML and set the ActionMessage.MethodName property. Add a property to the derived trigger of what key combination you are looking for and override the Invoke method to filter by that key combination, calling base.Invoke(...) if the key matches.
If you marshal a command through the View to the View Model you can control the CanExecute from the View Model. I've been using this method in multiple Caliburn projects. Might not be as "slick" as using Interactivity, but CanExecute works.
<UserControl x:Class="MyView"
...
Name="View"
>
<UserControl.InputBindings>
<KeyBinding Key="F5"
Command="{Binding RefreshCommand, ElementName=View, Mode=OneWay}" />
</UserControl.InputBindings>
<Button Command="{Binding Path=RefreshCommand, ElementName=View, Mode=OneWay}"/>
In your View class, you wire the command to the View Model which is referenced in the MyView.DataContext property.
Class MyView
Public Property RefreshCommand As _
New RelayCommand(AddressOf Refresh,
Function()
If ViewModel Is Nothing Then
Return False
Else
Return ViewModel.CanRefresh
End If
End Function)
Private Sub Refresh()
ViewModel.Refresh()
End Sub
Private ReadOnly Property ViewModel As MyViewModel
Get
Return DirectCast(DataContext, MyViewModel)
End Get
End Property
End Class
Related
This cannot be this difficult. The TreeView in WPF doesn't allow you to set the SelectedItem, saying that the property is ReadOnly. I have the TreeView populating, even updating when it's databound collection changes.
I just need to know what item is selected. I am using MVVM, so there is no codebehind or variable to reference the treeview by. This is the only solution I have found, but it is an obvious hack, it creates another element in XAML that uses ElementName binding to set itself to the treeviews selected item, which you must then bind your Viewmodel too. Several other questions are asked about this, but no other working solutions are given.
I have seen this question, but using the answer given gives me compile errors, for some reason I cannot add a reference to the blend sdk System.Windows.Interactivity to my project. It says "unknown error system.windows has not been preloaded" and I haven't yet figured out how to get past that.
For Bonus Points: why the hell did Microsoft make this element's SelectedItem property ReadOnly?
You should not really need to deal with the SelectedItem property directly, bind IsSelected to a property on your viewmodel and keep track of the selected item there.
A sketch:
<TreeView ItemsSource="{Binding TreeData}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
public class TViewModel : INotifyPropertyChanged
{
private static object _selectedItem = null;
// This is public get-only here but you could implement a public setter which
// also selects the item.
// Also this should be moved to an instance property on a VM for the whole tree,
// otherwise there will be conflicts for more than one tree.
public static object SelectedItem
{
get { return _selectedItem; }
private set
{
if (_selectedItem != value)
{
_selectedItem = value;
OnSelectedItemChanged();
}
}
}
static virtual void OnSelectedItemChanged()
{
// Raise event / do other things
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged("IsSelected");
if (_isSelected)
{
SelectedItem = this;
}
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
A very unusual but quite effective way to solve this in a MVVM-acceptable way is the following:
Create a visibility-collapsed ContentControl on the same View the TreeView is. Name it appropriately, and bind its Content to some SelectedSomething property in viewmodel. This ContentControl will "hold" the selected object and handle it's binding, OneWayToSource;
Listen to the SelectedItemChanged in TreeView, and add a handler in code-behind to set your ContentControl.Content to the newly selected item.
XAML:
<ContentControl x:Name="SelectedItemHelper" Content="{Binding SelectedObject, Mode=OneWayToSource}" Visibility="Collapsed"/>
<TreeView ItemsSource="{Binding SomeCollection}"
SelectedItemChanged="TreeView_SelectedItemChanged">
Code Behind:
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItemHelper.Content = e.NewValue;
}
ViewModel:
public object SelectedObject // Class is not actually "object"
{
get { return _selected_object; }
set
{
_selected_object = value;
RaisePropertyChanged(() => SelectedObject);
Console.WriteLine(SelectedObject);
}
}
object _selected_object;
You can create an attached property that is bindable and has a getter and setter:
public class TreeViewHelper
{
private static Dictionary<DependencyObject, TreeViewSelectedItemBehavior> behaviors = new Dictionary<DependencyObject, TreeViewSelectedItemBehavior>();
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TreeView))
return;
if (!behaviors.ContainsKey(obj))
behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView));
TreeViewSelectedItemBehavior view = behaviors[obj];
view.ChangeSelectedItem(e.NewValue);
}
private class TreeViewSelectedItemBehavior
{
TreeView view;
public TreeViewSelectedItemBehavior(TreeView view)
{
this.view = view;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
}
internal void ChangeSelectedItem(object p)
{
TreeViewItem item = (TreeViewItem)view.ItemContainerGenerator.ContainerFromItem(p);
item.IsSelected = true;
}
}
}
Add the namespace declaration containing that class to your XAML and bind as follows (local is how I named the namespace declaration):
<TreeView ItemsSource="{Binding Path=Root.Children}"
local:TreeViewHelper.SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}"/>
Now you can bind the selected item, and also set it in your view model to change it programmatically, should that requirement ever arise. This is, of course, assuming that you implement INotifyPropertyChanged on that particular property.
Use the OneWayToSource binding mode. This doesn't work. See edit.
Edit: Looks like this is a bug or "by design" behavior from Microsoft, according to this question; there are some workarounds posted, though. Do any of those work for your TreeView?
The Microsoft Connect issue: https://connect.microsoft.com/WPF/feedback/details/523865/read-only-dependency-properties-does-not-support-onewaytosource-bindings
Posted by Microsoft on 1/10/2010 at 2:46 PM
We cannot do this in WPF today, for the same reason we cannot support
bindings on properties that are not DependencyProperties. The runtime
per-instance state of a binding is held in a BindingExpression, which
we store in the EffectiveValueTable for the target DependencyObject.
When the target property is not a DP or the DP is read-only, there's
no place to store the BindingExpression.
It's possible we may some day choose to extend binding functionality
to these two scenarios. We get asked about them pretty frequently. In
other words, your request is already on our list of features to
consider in future releases.
Thanks for your feedback.
I decided to use a combination of code behind and viewmodel code. the xaml is like this:
<TreeView
Name="tvCountries"
ItemsSource="{Binding Path=Countries}"
ItemTemplate="{StaticResource ResourceKey=countryTemplate}"
SelectedValuePath="Name"
SelectedItemChanged="tvCountries_SelectedItemChanged">
Code behind
private void tvCountries_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
var vm = this.FindResource("vm") as ViewModels.CoiEditorViewModel;
if (vm != null)
{
var treeItem = sender as TreeView;
vm.TreeItemSelected = treeItem.SelectedItem;
}
}
And in the viewmodel there is a TreeItemSelected object which you can then access in the viewmodel.
You can always create a DependencyProperty that uses ICommand and listen to the SelectedItemChanged event on the TreeView. This can be a bit easier than binding IsSelected, but I imagine you will wind up binding IsSelected anyway for other reasons. If you just want to bind on IsSelected you can always have your item send a message whenever IsSelected changes. Then you can listen to those messages anyplace in your program.
I would like to use a progressbar in a simple way. I have a query that is run to return data to a grid when a user clicks a button. I would like to start the progressbar when the button is clicked and stop the progressbar when the data is returned to the grid.
I just want the progressbar to continue on (IsIndeterminate="True") to show that there is actually something happening.
Is there any way to bind the start and stop of the progressbar to properties or commands in my view model?
Thanks for any thoughts.
Use the IsIndeterminate property as your property to bind against a property on your ViewModel; mine is titled IsBusy in this example.
public partial class Window1 : Window
{
public MyViewModel _viewModel = new MyViewModel();
public Window1()
{
InitializeComponent();
this.DataContext = _viewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//this would be a command in your ViewModel, making life easy
_viewModel.IsBusy = !_viewModel.IsBusy;
}
}
public class MyViewModel : INotifyPropertyChanged
{
private bool _isBusy = false;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
PropertyChangedEventHandler handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("IsBusy"));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
The XAML is in this instance uses a click event handler for the Button; however in your instance you would simply bind your action which will start your processing to the command on your ViewModel.
<Grid>
<ProgressBar Width="100" Height="25" IsIndeterminate="{Binding IsBusy}"></ProgressBar>
<Button VerticalAlignment="Bottom" Click="Button_Click" Width="100" Height="25" Content="On/Off"/>
</Grid>
As you begin work and end work modifying the IsBusy property on your ViewModel will then start and stop the indeterminate behavior, providing the active/not-active visual appearance you are after.
You could expose a property that you then use to trigger the visibility of the ProgressBar, but you're better off using a control that encompasses a progress bar and exposes a property that turns it on/off. For example, the BusyIndicator in the Extended WPF Toolkit.
I am relatively new to WPF, XAML and Data-bindings. I have a view (Window) and a view-model.
I have tried to implement the MVVM pattern which means neither the view nor the view-model hold a reference to each other. All data exchange happens via data-bindings.
So far so good but now I have run into a problem I can't find a solution for.
On my view I have a button Start which is bound to a command.
<Button Command="{Binding NextCommand}" Content="Next">
NextCommand is of type ActionCommand : ICommand
In my case NextCommand simply calls a private method within the view-model.
The problem I can not find a solution so far is the following:
How to close the window at the end of the view-models NextCommandAction method?
private void NextCommandAction(object o)
{
...
...
// close the window
}
Since I do not have a reference to the view I can not just set DialogResult = true;
The only working solution I have found so far is to add a hidden radio-button to the view and bind it's value to a property CloseView and create a method CloseView within the xaml.cs file which is bound to the Checked event of the hidden radio-button. Within that method I set DialogResult = true;
Although this works I feel like there has to be a better solution than adding hidden elements to your view!
You can pass the window reference as CommandParameter to the Close command and do whatever required on the window.
<Button Content="Close" Command="{Binding Path=CloseCommand}"
CommandParameter="{Binding ElementName=Window}"/>
private void CloseCommand(object sender)
{
Window wnd = sender as Window;
wnd.Close();
}
CommandParameter="{Binding ElementName=Window}" assumes that you have an element in your XAML named "Window". e.g, your Window tag would need Name="Window"
This question was one of the first things that came up when I googled to check if DialogResult is a dependency property (it isn't :-) )
Add a dependency property to your Window:
public static readonly DependencyProperty InteractionResultProperty =
DependencyProperty.Register(
nameof(InteractionResult),
typeof(Boolean?),
typeof(MyWpfWindow1),
new FrameworkPropertyMetadata(default(Boolean?),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnInteractionResultChanged));
public Boolean? InteractionResult
{
get => (Boolean?) GetValue(InteractionResultProperty);
set => SetValue(InteractionResultProperty, value);
}
private static void OnInteractionResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((MyWpfWindow1) d).DialogResult = e.NewValue as Boolean?;
}
I named my property InteractionResult though a good name would have also worked.
In the xaml right after the
you can bind it with a style
<Window.Style>
<Style TargetType="{x:Type z:MyWpfWindow1}">
<Setter Property="InteractionResult"
Value="{Binding UpdateResult}" />
</Style>
</Window.Style>
UpdateResult is the property in my viewmodel.
private Boolean? _updateResult;
public Boolean? UpdateResult
{
get => _updateResult;
set => SetValue(ref _updateResult, value);
}
The SetValue method is the usual notify property
protected virtual Boolean SetValue<T>(ref T field, T value,
[CallerMemberName]String propertyName = null)
{
if (Equals(field, value))
return false;
field = value;
RaisePropertyChanged(propertyName);
return true;
}
and the property gets set in the usual way
<Button Content="Cancel"
Command="{Binding CancelCommand}" />
ICommand CancelCommand { get; }
private void OnCancel()
{
UpdateResult = false;
}
Disclaimer: works on my computer.
Inspired by Chandrashekhar Joshi's answer
(but not using the elements's name):
Define CommandParameter in Button:
<Button
Command="{Binding CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Content="Close" />
Define Command (and Implementation):
CloseCommand = new DelegateCommand<Window>((w) => w.DialogResult = true);
I'm new to MVVM and trying to figure out how to close a ChildWindow with the traditional Cancel button using MVVM Light Toolkit.
In my ChildWindow (StoreDetail.xaml), I have :
<Button x:Name="CancelButton" Content="Cancel" Command="{Binding CancelCommand}" />
In my ViewModel (ViewModelStoreDetail.cs), I have :
public ICommand CancelCommand { get; private set; }
public ViewModelStoreDetail()
{
CancelCommand = new RelayCommand(CancelEval);
}
private void CancelEval()
{
//Not sure if Messenger is the way to go here...
//Messenger.Default.Send<string>("ClosePostEventChildWindow", "ClosePostEventChildWindow");
}
private DelegateCommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
_cancelCommand = new DelegateCommand(CloseWindow);
return _cancelCommand;
}
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
If you displayed your child window by calling ShowDialog(), then you can simply set the IsCancel property of your button control to "True".
<Button Content="Cancel" IsCancel="True" />
It becomes the same as clicking the X button on the window, or pressing ESC on the keyboard.
Have a look at this articleon MSDN. About half way down there is an approach on how to do this. Basically it uses either uses a WorkspaceViewModel or you implements an interface that exposes and event RequestClose
You then inside the Window's DataContext (if you are setting the ViewModel to it) you can attach to the event.
This is an excerpt from the article (Figure 7). You can adjust it to suit your needs.
// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
string path = "Data/customers.xml";
var viewModel = new MainWindowViewModel(path);
// When the ViewModel asks to be closed,
// close the window.
viewModel.RequestClose += delegate
{
window.Close();
};
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
It's been a while since I've used WPF and MVVMLight but yes I think I'd use the messanger to send the cancel event.
In MVVM Light Toolkit the best what you can do is to use Messenger to interact with the View.
Simply register close method in the View (typically in the code behind file) and then send request to close a window when you need it.
We have implemented a NO-CODE BEHIND functionality. See if it helps.
EDIT: Here is there Stackoverflow discussion
Here are some ways to accomplish it.
Send message to your childwindow and set DialogueResult to false on childwindow code-behind.
Make property of DialogueResult and Bind it with childwindow Dialoue CLR property, set it on CancelEval method of CancelCommand.
Create object of Childwindow and set DialogueResult false on CancelEval.
Kind of late to the party but I thought I'd add my input. Borrowing from user841960's answer:
public RelayCommand CancelCommand
{
get;
private set;
}
Then:
SaveSettings = new RelayCommand(() => CloseWindow());
Then:
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
It's a bit cleaner than using an ICommand and works just as well.
So, to sum it all up, the example class would look like so:
public class ChildViewModel
{
public RelayCommand CancelCommand
{
get;
private set;
}
public ChildViewModel()
{
SaveSettings = new RelayCommand(() => CloseWindow());
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
}
i have an XamDataPresenter (XamDataGrid) bound to a collection in the ViewModel:
XAML:
<igDP:XamDataPresenter x:Name="dataPresenter" DataSource="{Binding Path=AppServers, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True">
</igDP:XamDataPresenter>
Code:
public ShellViewModel()
{
AppServers = new BindingListCollectionView(new BindingList<AppServer>(_context.GetAllAppServers()));
AppServers.CurrentChanged += new EventHandler(AppServers_CurrentChanged);
}
void AppServers_CurrentChanged(object sender, EventArgs e)
{
NotifyOfPropertyChange(() => CanSaveAppServers);
NotifyOfPropertyChange(() => CanDeleteAppServers);
}
The CanSaveAppServers property:
public bool CanSaveAppServers
{
get
{
return (_appServers.SourceCollection as BindingList<AppServer>).Any(x => x.ChangeTracker.State != ObjectState.Unchanged);
}
}
The CanSaveAppServers property should be false if an item of the collection is changed. But how is the CanSaveAppServers called? Another event? Or the wrong collection type? Shouldn't this be done automatically in some way?
Thanks in advance.
If you are letting Caliburn bind via naming conventions, then you have a public method named SaveAppServers. Caliburn creates an ICommand that is bound to the Button so that when the button is clicked, ICommand's Execute() is called. In the meantime, there is a CanExecute() method on ICommand that is used to determine whether the button is enabled or not.
When you call NotifyOfPropertyChange(() => CanSaveAppServers), this ends up making the ICommand raise its CanExecuteChanged event, which makes WPF refresh by calling CanExecute() again, which under the covers is getting CanSaveAppServers.