I have a MainView whose DataContext is my MainViewModel.
MainViewModel:
class MainViewModel : PropertyChangedBase
{
#region Properties
/// <summary>
/// The ProjectViewModel.
/// </summary>
public ProjectViewModel ProjectVM
{
get { return _projectVM; }
private set
{
_projectVM = value;
NotifyOfPropertyChange(() => ProjectVM);
}
}
private ProjectViewModel _projectVM;
#endregion
/// <summary>
/// Constructor.
/// </summary>
public MainViewModel()
{
ProjectVM = new ProjectViewModel();
}
}
Now, I have a Menu on my MainView. I want to bind the Click event of the MenItems to methods on the ProjectVM object. of course I know I can just set the DataContext of the MenuItems, but I was wondering if there is a simpler way.
Currently my MainView looks like this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Menu Grid.Row="0">
<MenuItem Header="File">
<MenuItem Header="New Project...">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="ProjectVM.ShowNewProjectDialog"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</MenuItem>
<MenuItem Header="Load Project..."/>
<MenuItem Header="Close Project..."/>
</MenuItem>
</Menu>
I had hopes Caliburn was smart enough to resolve ProjectVM.ShowNewProjectDialog, but it isn't. Is there any good way to do this without having to set the DataContext of the Menu manually?
You are right, Caliburn is not so smart to parse the MethodName property in the way that you wish. Anyway it is a powerful tool which can be easily customized depending on your needs.
As you can read in the Caliburn Micro Documentation section named All About Actions:
ActionMessage is, of course, the Caliburn.Micro-specific part of this
markup. It indicates that when the trigger occurs, we should send a
message of “SayHello.” So, why do I use the language “send a message”
instead of “execute a method” when describing this functionality?
That’s the interesting and powerful part. ActionMessage bubbles
through the Visual Tree searching for a target instance that can
handle it.
It means that - if you need - you can manually set the "target" which will handle your message. You can do it by using the Action.Target attached property. Of course you do not want to set it for each MenuItem, so you can set directly in your Menu object:
<Menu cal:Action.Target="{Binding Path=ProjectVM, Mode=OneWay}">
<MenuItem Header="File">
<MenuItem Header="New Project..." cal:Message.Attach="ShowNewProjectDialog" />
<MenuItem Header="Load Project..."/>
<MenuItem Header="Close Project..."/>
</MenuItem>
</Menu>
By setting the Action.Target attached property we are declaring that all messages (i.e. ActionMessages) which come from a Menu's child will be handled by the ProjectViewModel.
Now if you run your project you will see that it does not work property. The reason is that Caliburn Micro uses the VisualTreeHelper for traversing the XAML tree. For our purpose we need to use the LogicalTreeHelper.
So the last step is to add this code in the Bootstrapper Configure method:
ActionMessage.SetMethodBinding = delegate(ActionExecutionContext context)
{
FrameworkElement source = context.Source;
for (DependencyObject dependencyObject = source; dependencyObject != null; dependencyObject = LogicalTreeHelper.GetParent(dependencyObject))
{
if (Caliburn.Micro.Action.HasTargetSet(dependencyObject))
{
object handler = Message.GetHandler(dependencyObject);
if (handler == null)
{
context.View = dependencyObject;
return;
}
MethodInfo methodInfo = ActionMessage.GetTargetMethod(context.Message, handler);
if (methodInfo != null)
{
context.Method = methodInfo;
context.Target = handler;
context.View = dependencyObject;
return;
}
}
}
if (source != null && source.DataContext != null)
{
object dataContext = source.DataContext;
MethodInfo methodInfo2 = ActionMessage.GetTargetMethod(context.Message, dataContext);
if (methodInfo2 != null)
{
context.Target = dataContext;
context.Method = methodInfo2;
context.View = source;
}
}
};
I hope it can help you.
Related
I am trying to bind an ObservableCollection of objects to a TreeView. The object class is shown below
public partial class Layer_Properties : Window
{
//fields
private string _routeName;
private List<Stop> _Stops_List = new List<Stop>();
public string routeName // property
{
get { return _routeName; } // get method
set { _routeName = value; } // set method
}
public List<Stop> Stops_List // property
{
get { return _Stops_List; } // get method
set { _Stops_List = value; } // set method
}
public List<PolylineBarrier> polylineBarriers// property
{
get { return _polylineBarriers; } // get method
set { _polylineBarriers= value; } // set method
}
public Layer_Properties(RouteTask asolveRouteTask, MapViewModel aMapViewModel)
{
InitializeComponent();
solveRouteTask = asolveRouteTask;
_mapViewModel = aMapViewModel;
this.Loaded += async (o, e) =>
{
await Task.Run(() => getnetworkDatasetProprties(solveRouteTask));
routeGUID = Convert.ToString(Guid.NewGuid());
dateTime_label.IsEnabled = false;
dateTime_ComboBox.IsEnabled = false;
this.dateTime_ComboBox.Value = DateTime.UtcNow;
Use_Time_Windows_chkbox.IsEnabled = false;
Use_Time_Windows_chkbox.IsEnabled = false;
Use_Time_Windows_chkbox.IsEnabled = false;
PreserveFirstStop_chkbox.IsChecked = true;
PreserveLastStop_chkbox.IsChecked = true;
PreserveFirstStop_chkbox.IsEnabled = false;
PreserveLastStop_chkbox.IsEnabled = false;
Layer_Properties item = null;
if (_mapViewModel.LayersPool.Count <= 1)
{
item = _mapViewModel.LayersPool[_mapViewModel.LayersPool.Count - 1];
}
else
{
item = _mapViewModel.LayersPool[_mapViewModel.LayersPool.Count - 2];
}
if (item != null)
{
item.routeName = IndexedFilename("Route", item.routeName);
}
};
}
Part of the mapviewmodel is shown below:
public class MapViewModel : INotifyPropertyChanged
{
public MapViewModel()
{
}
//test 09062020
public ObservableCollection<Layer_Properties> LayersPool
{
get { return layersPool; }
set
{
layersPool = value;
NotifiyPropertyChanged("LayersPool");
}
}
private ObservableCollection<Layer_Properties> layersPool= new
ObservableCollection<Layer_Properties>();
void NotifiyPropertyChanged(string property)
{
if (LayersPoolChanged != null)
LayersPoolChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler LayersPoolChanged;
//endtest
}
Part of the xaml is shown below:
<Window x:Class="GIS_App.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:esri="http://schemas.esri.com/arcgis/runtime/2013"
xmlns:local="clr-namespace:GIS_App"
mc:Ignorable="d"
Title="MainWindow" Height="732" Width="1399" Closing="Window_Closing">
<Window.Resources>
<local:MapViewModel x:Key="MapViewModel"/>
<ImageBrush x:Key="NetworkAnalystWindow" ImageSource="/icons/NetworkAnalystWindow.png" Stretch="UniformToFill"/>
<ImageBrush x:Key="AddNetworkElement" ImageSource="/icons/AddNetworkElement_btn.png" Stretch="UniformToFill"/>
<ImageBrush x:Key="solveRoute" ImageSource="/icons/solve_btn.png" Stretch="UniformToFill"/>
<ImageBrush x:Key="solvePremiumRoute" ImageSource="/icons/solvePremium_btn.png" Stretch="UniformToFill"/>
<ImageBrush x:Key="RouteDirections" ImageSource="/icons/directions_btn.png" Stretch="UniformToFill"/>
<ImageBrush x:Key="AddTrafficLayer" ImageSource="/icons/AddTrafficLayer.png" Stretch="UniformToFill"/>
<ContextMenu x:Key="cmButton">
<MenuItem Name ="Draw_Sketch" Header="Draw Sketch" Click="btn_Click"/>
<MenuItem Name ="Save_Sketch" Header="Save Sketch" Click="btn_Click"/>
<MenuItem Name ="Delete_Sketch" Header="Delete Sketch" Click="btn_Click" IsEnabled="True"/>
<MenuItem Name ="Remove_Selected_Vertex" Header="Remove Selected Vertex" Click="btn_Click" IsEnabled="True"/>
<MenuItem Name ="Cancel" Header="Cancel" Click="btn_Click" IsEnabled="True"/>
<Separator />
<MenuItem Header="Address Geocoding" Click="btn_Click" IsEnabled="True"/>
</ContextMenu>
<ContextMenu x:Key="treeViewMenu">
<MenuItem Name ="Delete_TreeViewElement" Header="Delete" Click="btn_Click"/>
<MenuItem Name ="Delete_All_TreeViewElements" Header="Delete All" Click="btn_Click"/>
<MenuItem Name ="Open_Attribute_Table" Header="Open_Attribute_Table" Click="btn_Click" IsEnabled="True"/>
</ContextMenu>
</Window.Resources>
<Grid HorizontalAlignment="Left" Height="Auto" Margin="5,25,0,20" VerticalAlignment="Stretch" Width="155">
<!-- hieracical binding -->
<TextBlock Text="Hierarchical root binding}" Foreground="Red" Margin="10,20,0,0"/>
<TreeView ItemsSource="{Binding LayersPool}" Margin="10" Height="200">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding LayersPool}" DataType="{x:Type local:Layer_Properties}">
<TreeViewItem Header="{Binding routeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
i ve made numerous attempts to make it work but i cannot find a solution. What i want to achieve is create a treeview as follows:
Route_1
Stops
Stop1
Stop2
Stop3
Polyline Barriers
Barrier 1
Route_2
Stops
Stop1
Stop2
Polyline Barriers
Barrier 1
Barrier2
Edit I suspect there might be an issue with binding since LayersPool observablecollection is a mapviewmodel collection. Any help will be welcome.
I think you need to understand how TreeView binding works.
You can have a look here: wpf treeview binding and also here: https://www.wpf-tutorial.com/treeview-control/treeview-data-binding-multiple-templates/
Generally speaking, if each TreeViewItem ViewModel needs to do something specific (for example once the user click on it, then you need to create your own ViewModel specific class for each TreeViewItem.
You might create an AbstractViewModel as a base class for every TreeViewItem ViewModel that contains basically 2 properties.
Children binded to ItemsSource in the HierarchicalDataTemplate that contains an ObservableCollection of current treeviewitem's leaves
Name binded to your Text property of the TextBlock you placed in your DataTemplate to show the treeview item text.
I am trying to show a ContextMenu in a TreeView. Some entries must be available whether an item was selected or not, but all commands are disabled until I populate the TreeView with at least one item:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Command="New" IsEnabled="True" />
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
However, the menu item is still disabled:
The very same command is enabled in the File menu in the menu bar and there is no CanExecute attribute.
How can I enable the context menu entry even if no item exists?
The issue is that the DataContext of the ContextMenu (i.e. where it's looking to bind the New command) is the tree-view node, not the tree view itself. Great if you've got commands related to the node - editing, moving, changing settings.
Not so good for the few that are pan-node like adding and deleting.
As it's looking in the node's DataContext (and no nodes exit) it can't find the command (and it doesn't make sense for it to be there anyway, as the object that manages the TreeView should be creating new items, not the items themselves).
The solution is to bind to a New command that's not in the DataContext of the item, but the TreeView. There's the frustration of dealing with data-binding with ContextMenu... as it's not in the same visual tree as the rest of the window it's often frustrating to deal with.
A solution is to reference the PlacementTarget of the context menu like this:
<TreeView Name="myTreeView" Width="200px">
<TreeView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit (This command exists in the Node's ViewModel)" Command="{Binding Edit}"/>
<MenuItem Header="New (This command exists in the Window's ViewModel)" Command="{Binding PlacementTarget.DataContext.New, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
</ContextMenu>
</TreeView.ContextMenu>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding Path=Title}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Further questions
An example of adding a command as a static resource (Change Window to UserControl if you're in a view that's a UserControl):
<Window.Resources>
<local:MyCommand x:Key="MyCommand"/>
</Window.Resources>
Then referenced with:
<MenuItem Header="MyCommand" Command="{StaticResource MyCommand}"/>
Binding to your commands in the ViewModel (i.e. DataContext) is done like in the first example. In the same way you bind the Title, you can bind to any property, such as an ICommand.
So for a view:
<MenuItem Header="New" Command="{Binding New}"/>
The View Model has a property NewCommand named New:
public NewCommand New { get; private set; }
People often use this because they have a generic ICommand that takes a delegate so they can configure all the actions that relate to that ViewModel. For example:
public class MyCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public Action<object> Action { get; set; }
public MyCommand(Action<object> action)
{
Action = Action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Action(parameter);
}
}
Then in the ViewModel, instead of having loads of ICommand classes all implemented, we can just re-use this and get it to do different things:
public MyCommand New { get; private set; }
public MyCommand Delete { get; private set; }
public MyCommand ClearAll { get; private set; }
public MyViewModelConstructor()
{
New = new MyCommand((parameter) =>
{
//Add new object
});
Delete = new MyCommand((parameter) =>
{
//Delete object
});
ClearAll = new MyCommand((parameter) =>
{
//Clear all objects
});
}
I am trying to use AvalonDock in Prism. All works fine except MenuItem "Tools->Properties". The MenuItem "File->New" works fine - new window is created and I can dock wherever I want.
This code works okay in simple MVVM application(without Prism) – “MenuItem” is fired always.
What I have:
<UserControl x:Class="ModuleCAvalonDock.ViewC">
<Menu>
<MenuItem Header="File">
<MenuItem Header="New" Command="{Binding NewCommand}"/>
<MenuItem Header="Open" Command="{Binding OpenCommand}"/>
<Separator/>
<MenuItem Header="Save" Command="{Binding ActiveDocument.SaveCommand}"/>
<MenuItem Header="Save As..." Command="{Binding ActiveDocument.SaveAsCommand}"/>
<Separator/>
<MenuItem Header="Close" Command="{Binding ActiveDocument.CloseCommand}"/>
</MenuItem>
<MenuItem Header="Tools">
<MenuItem Header="Properties" IsChecked="{Binding FileStats.IsVisible, Mode=TwoWay}" IsCheckable="True"/>
</MenuItem>
<MenuItem Header="Layout">
<MenuItem Header="Load" Command="{Binding LoadLayoutCommand, ElementName=mainWindow}"/>
<MenuItem Header="Save" Command="{Binding SaveLayoutCommand, ElementName=mainWindow}"/>
<MenuItem Header="Dump to Console" Click="OnDumpToConsole"/>
</MenuItem>
</Menu>
</UserControl>
How I am binding:
public ViewC(IViewModel viewModel)
{
InitializeComponent();
this.DataContext = Workspace.This;
}
and ViewModel:
public class Workspace:ViewModelBase
{
FileStatsViewModel _fileStats = null;
public FileStatsViewModel FileStats
{
get
{
if (_fileStats == null)
_fileStats = new FileStatsViewModel();
return _fileStats;
}
}
RelayCommand _newCommand = null;
public ICommand NewCommand
{
get
{
if (_newCommand == null)
{
_newCommand = new RelayCommand((p) => OnNew(p), (p) => CanNew(p));
}
return _newCommand;
}
}
private bool CanNew(object parameter)
{
return true;
}
private void OnNew(object parameter)
{
_files.Add(new FileViewModel());
ActiveDocument = _files.Last();
}
protected Workspace()
{
}
static Workspace _this = new Workspace();
public static Workspace This
{
get { return _this; }
}
}
FileStatsViewModel:
public class FileStatsViewModel : ToolViewModel { }
public class ToolViewModel
{
private bool _isVisible = true;
public bool IsVisible
{
get { return _isVisible; }
set
{
if (_isVisible != value)
{
_isVisible = value;
RaisePropertyChanged("IsVisible");
}
}
}
}
However, It does not work in Prism. I’ve tried setting some break point to check if the property is fired and FileStats property is firing in simple MVVM application, but FileStats property is not firing in Prism application!
The code where I initialize my View:
public class ModuleCModule: ModuleBase
{
public ModuleCModule(IUnityContainer container, IRegionManager regionManager)
: base(container, regionManager) { }
protected override void InitializeModule()
{
RegionManager.RegisterViewWithRegion(RegionNames.ContentRegion, typeof(ViewC));
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<ViewC>();
}
}
It is really interesting that I can fire my command <MenuItem Header="New" Command="{Binding NewCommand}"/>. However, I cannot fire property FileStats at viewModel Workspace.
How to fire my property? What am I doing wrong?
Prism has no impact on the WPF binding system, so this is an issue with how you are creating the binding to the DataContext of the view. How are you assigning the ViewModel to the DataContext of your View? Check your output window for binding errors. Chances are your DataContext is not being set.
If you are using the ViewModelLocator, then make sure you are following the proper naming conventions:
http://brianlagunas.com/getting-started-prisms-new-viewmodellocator/
EDIT: I ran your app as is, and was able to hit the setter on your FileStats.IsVisible property. If you are placing a breakpoint on the getter of FileStats, well that won't fire when you click on the menu item, because you are bound to a property of FileStats, not FileStats which is read-only anyways. By the way, you know Prism 6.1 is out (you're using v4). Also, you should try using NuGet instead of hard references.
I have used AvalonEdit control in my project. When I use shortcut keys like Ctrl+C or Ctrl+V, associated copy/paste commands works fine. I decided to use these commands in context menu for more usability because some users get used to right-click instead of shortcut. I used the following XAML code for control:
<avalonedit:TextEditor.ContextMenu>
<ContextMenu>
<MenuItem Command="Undo" />
<MenuItem Command="Redo" />
<Separator/>
<MenuItem Command="Cut" />
<MenuItem Command="Copy" />
<MenuItem Command="Paste" />
</ContextMenu>
</avalonedit:TextEditor.ContextMenu>
but when I run the program these commands are always shown disabled in the context menu as follows:
When I first encountered this problem I posted a different question but with the help of MD.Unicorn ( as you see in the comments below) I realized that when you place AvalonEdit in the ItemTemplate of a ListBox or ListView commands doesn't work.
With the help of MD.unicorn I created the following testing code to reproduce the result:
ViewModel Class and a simple class for data template
public class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
collection = new ObservableCollection<myClass>();
mc = new myClass();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propName)
{
var h = PropertyChanged;
if (h != null)
h(this, new PropertyChangedEventArgs(propName));
}
public ObservableCollection<myClass> collection { get; set; }
public myClass mc { get; set; }
}
public class myClass
{
public string text { get; set; }
}
public partial class MainWindow : Window
{
MyViewModel _viewModel = new MyViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = _viewModel;
}
}
and XAML code for MainWindow
<Window.Resources>
<DataTemplate DataType="{x:Type local:myClass}">
<StackPanel>
<avalonedit:TextEditor x:Name="xmlMessage"
SyntaxHighlighting="XML" ShowLineNumbers="True" >
<avalonedit:TextEditor.ContextMenu>
<ContextMenu>
<MenuItem Command="Undo" />
<MenuItem Command="Redo" />
<Separator/>
<MenuItem Command="Cut" />
<MenuItem Command="Copy" />
<MenuItem Command="Paste" />
</ContextMenu>
</avalonedit:TextEditor.ContextMenu>
</avalonedit:TextEditor>
<TextBox Text="test" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<ListView ItemsSource="{Binding collection}" />
<ContentControl Content="{Binding mc}" />
</DockPanel>
If you try this test, you can see that if DataTemplate is used on a content control its command binding in context menu works fine but in ListViewItem they are disabled.
Also note that context menu in DataTemplate works fine for TextBox and shows that ListView by itself doesn't inherently breaks the command chain.
How can I fix context menu and hook up to control commands in listView items?
This is what I used to get past similar issues - I hope it's useful to people (this general logic can be applied to a wide array of Avalon editor related issues)...
What actually happens is probably the Avalon's fault (in combination with ListItem etc.). It messes up the mouse handling and I'm guessing the focus (which should be on the TextArea for commands and CanExecute to work.
The mouse handling is the issue - as if you just press windows
context menu key it pops up a regular menu with enabled commands.
Avalon editor has a complex mouse/key handling (it's hard to make a
good editor) - and on keyboard it does an explicit 'focus' on the
TextArea. You can also see the issue by putting a breakpoint on the
CanCutOrCopy method (Editing/EditingCommandHandler.cs, download
the Avalon source) which actually handles the
ApplicationCommands.Copy. For the 'keyboard' menu it first goes in
there, then pops up. For the 'mouse' one, it pops up - and then on
exit it checks the CanExecute (enters that method). That's all
wrong!
And the errata...
There are no problems with your own commands, just expose your commands normally and all should work.
For ApplicationCommands (i.e. the RoutedCommand) it doesn't wire up properly - and the Execute, CanExecute don't go where it should, i.e. the TextArea. To correct that you need to rewire the commands into your own wrappers - and basically call the TextArea handling - which is just a few lines of code, but it's a necessary step (I don't think there is a more 'beautiful' solution to this, short of fixing the Avalon code - which may be a pain, never crossed my mind).
(all is based on your example - fill in the blanks where I left out)
Your XAML:
<Window.Resources>
<DataTemplate DataType="{x:Type my:myClass}">
<StackPanel>
<my:AvalonTextEditor x:Name="xmlMessage" SyntaxHighlighting="XML" ShowLineNumbers="True" EditText="{Binding text}" >
<my:AvalonTextEditor.ContextMenu>
<ContextMenu x:Name="mymenu1">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="CommandParameter" Value="{Binding Path=., RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"/>
</Style>
</ContextMenu.Resources>
<MenuItem Header="My Copy" Command="{Binding CopyCommand}" />
<MenuItem Header="My Paste" Command="{Binding PasteCommand}" />
<MenuItem Header="My Cut" Command="{Binding CutCommand}" />
<MenuItem Header="My Undo" Command="{Binding UndoCommand}" />
<MenuItem Header="My Redo" Command="{Binding RedoCommand}" />
<Separator />
<MenuItem Command="Undo" />
<MenuItem Command="Redo" />
<Separator/>
<MenuItem Command="Cut" />
<MenuItem Command="Copy" />
<MenuItem Command="Paste" />
</ContextMenu>
</my:AvalonTextEditor.ContextMenu>
</my:AvalonTextEditor>
</StackPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<DockPanel>
<ListView ItemsSource="{Binding collection}" />
<ContentControl Content="{Binding mc}" />
</DockPanel>
</StackPanel>
The code behind - view model:
(note: I left the naming as you did put it - but please do not use small-caps for props :)
public class MyViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public MyViewModel()
{
collection = new ObservableCollection<myClass>(new[]
{
new myClass{ text = "some more test - some more test - some more test - some more test - some more test - some more test - some more test - some more test - some more test - " },
new myClass{ text = "test me test me = test me test me = test me test me = test me test me = test me test me = test me test me = " },
new myClass{ text = "test again - test again - test again - test again - test again - " },
new myClass{ text = "test again - test again - " },
new myClass{ text = "test again - " },
new myClass{ text = "test" },
});
mc = new myClass();
}
public ObservableCollection<myClass> collection { get; set; }
public myClass mc { get; set; }
}
public class myClass
{
public string text { get; set; }
AvalonRelayCommand _copyCommand;
public AvalonRelayCommand CopyCommand
{ get { return _copyCommand ?? (_copyCommand = new AvalonRelayCommand(ApplicationCommands.Copy) { Text = "My Copy" }); } }
AvalonRelayCommand _pasteCommand;
public AvalonRelayCommand PasteCommand
{ get { return _pasteCommand ?? (_pasteCommand = new AvalonRelayCommand(ApplicationCommands.Paste) { Text = "My Paste" }); } }
AvalonRelayCommand _cutCommand;
public AvalonRelayCommand CutCommand
{ get { return _cutCommand ?? (_cutCommand = new AvalonRelayCommand(ApplicationCommands.Cut) { Text = "My Cut" }); } }
AvalonRelayCommand _undoCommand;
public AvalonRelayCommand UndoCommand
{ get { return _undoCommand ?? (_undoCommand = new AvalonRelayCommand(ApplicationCommands.Undo) { Text = "My Undo" }); } }
AvalonRelayCommand _redoCommand;
public AvalonRelayCommand RedoCommand
{ get { return _redoCommand ?? (_redoCommand = new AvalonRelayCommand(ApplicationCommands.Redo) { Text = "My Redo" }); } }
}
(note: just wire up the Window.DataContext to view-model, as you did)
And two custom classes required for this to wrap.
public class AvalonTextEditor : TextEditor
{
#region EditText Dependency Property
public static readonly DependencyProperty EditTextProperty =
DependencyProperty.Register(
"EditText",
typeof(string),
typeof(AvalonTextEditor),
new UIPropertyMetadata(string.Empty, EditTextPropertyChanged));
private static void EditTextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
AvalonTextEditor editor = (AvalonTextEditor)sender;
editor.Text = (string)e.NewValue;
}
public string EditText
{
get { return (string)GetValue(EditTextProperty); }
set { SetValue(EditTextProperty, value); }
}
#endregion
#region TextEditor Property
public static TextEditor GetTextEditor(ContextMenu menu) { return (TextEditor)menu.GetValue(TextEditorProperty); }
public static void SetTextEditor(ContextMenu menu, TextEditor value) { menu.SetValue(TextEditorProperty, value); }
public static readonly DependencyProperty TextEditorProperty =
DependencyProperty.RegisterAttached("TextEditor", typeof(TextEditor), typeof(AvalonTextEditor), new UIPropertyMetadata(null, OnTextEditorChanged));
static void OnTextEditorChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContextMenu menu = depObj as ContextMenu;
if (menu == null || e.NewValue is DependencyObject == false)
return;
TextEditor editor = (TextEditor)e.NewValue;
NameScope.SetNameScope(menu, NameScope.GetNameScope(editor));
}
#endregion
public AvalonTextEditor()
{
this.Loaded += new RoutedEventHandler(AvalonTextEditor_Loaded);
}
void AvalonTextEditor_Loaded(object sender, RoutedEventArgs e)
{
this.ContextMenu.SetValue(AvalonTextEditor.TextEditorProperty, this);
}
}
public class AvalonRelayCommand : ICommand
{
readonly RoutedCommand _routedCommand;
public string Text { get; set; }
public AvalonRelayCommand(RoutedCommand routedCommand) { _routedCommand = routedCommand; }
public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
public bool CanExecute(object parameter) { return _routedCommand.CanExecute(parameter, GetTextArea(GetEditor(parameter))); }
public void Execute(object parameter) { _routedCommand.Execute(parameter, GetTextArea(GetEditor(parameter))); }
private AvalonTextEditor GetEditor(object param)
{
var contextMenu = param as ContextMenu;
if (contextMenu == null) return null;
var editor = contextMenu.GetValue(AvalonTextEditor.TextEditorProperty) as AvalonTextEditor;
return editor;
}
private static TextArea GetTextArea(AvalonTextEditor editor)
{
return editor == null ? null : editor.TextArea;
}
}
Notes:
EditText is just a dependency property - to be able to bind a Text (your text) - that's an Avalon shortcoming. Here just for fun but you may need it, so I left it in.
Use AvalonRelayCommand to rewire the Application routed commands - for other stuff use your own Command implementation. Those two classes are the core.
You need to use AvalonTextEditor instead of TextEditor - which is just a tiny wrapper - to hook up ContextMenu with the TextEditor (apart from other problems, menu items are suffering from lack of visual tree - and you can't get any controls from it that easily). And we need to get a ref to TextEditor from the CommandParameter (which is set to be a ContextMenu). This could've been done with just some attachment properties (w/o overriding the TextEditor), but seems cleaner this way.
On the XAML side - just a few small changes - use the wrapper editor - and you have a MenuItem style which injects the right parameter for each command (you can do it some other way, this was nicer).
It's not a hack - we're just shortcutting the shortcoming of the
mouse handling - by manually calling the TextArea command handling.
That's pretty much it.
Enjoy!
I have just started learning MVVM. I've made the application from scratch by following this MVVM tutorial (I highly recommend it to all MVVM beginners out there). Basically, what I have created so far is a couple of text boxes where user adds his or her data, a button to save that data which subsequently populates the ListBox with all entries made.
Here's where I got stuck: I want to be able to double-click on a ListBoxItem and to trigger a command that I have created and added to my ViewModel. I don't know how to finish the XAML side, i.e. I don't know how to bind that command to the ListBox(Item).
Here's XAML:
...
<ListBox
Name="EntriesListBox"
Width="228"
Height="208"
Margin="138,12,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemsSource="{Binding Entries}" />
...
Here's ViewModel:
public class MainWindowViewModel : DependencyObject
{
...
public IEntriesProvider Entries
{
get { return entries; }
}
private IEntriesProvider entries;
public OpenEntryCommand OpenEntryCmd { get; set; }
public MainWindowViewModel(IEntriesProvider source)
{
this.entries = source;
...
this.OpenEntryCmd = new OpenEntryCommand(this);
}
...
}
And finally, here's the OpenEntryCommand that I want to be executed once the user double-clicks the item in the EntriesListBox:
public class OpenEntryCommand : ICommand
{
private MainWindowViewModel viewModel;
public OpenEntryCommand(MainWindowViewModel viewModel)
{
this.viewModel = viewModel;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return parameter is Entry;
}
public void Execute(object parameter)
{
string messageFormat = "Subject: {0}\nStart: {1}\nEnd: {2}";
Entry entry = parameter as Entry;
string message = string.Format(messageFormat,
entry.Subject,
entry.StartDate.ToShortDateString(),
entry.EndDate.ToShortDateString());
MessageBox.Show(message, "Appointment");
}
}
Please help, I'd appreciate it.
Unfortunately, only ButtonBase derived controls have the possibility for binding ICommand objects to their Command properties (for the Click event).
However, you can use an API provided by Blend to map an event (like in your case MouseDoubleClick on the ListBox) to an ICommand object.
<ListBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding YourCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
You'll have to define: xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" and have a reference to System.Windows.Interactivity.dll.
-- EDIT --
This is part of WPF4, but u can use Microsoft.Windows.Interactivity if you're not using WPF4. This dll is from Blend SDK, which doesn't require Blend, from here:
http://www.microsoft.com/downloads/en/details.aspx?FamilyID=f1ae9a30-4928-411d-970b-e682ab179e17&displaylang=en
Update: I found something that should help you. check this link on MVVM Light Toolkit which contains a walkthrough on how to do this, along with a link to the needed libraries. MVVM Light Toolkit is a very interesting framework for applying MVVM with Silverlight, WPF, and WP7.
Hope this helps :)
This is made tricky because of the DoubleClick event. There are a few ways to do this:
Handle the double-click event in code behind, and then manually invoke a command/method on your ViewModel
Use an attached behavior to route the DoubleClick event to your Command
Use a Blend Behavior to map the DoubleClick event to your command
2 and 3 might be more pure, but frankly, 1 is easier, less complex, and not the worst thing in the world. For a one-off case, I'd probably use approach #1.
Now, if you changed your requirements to use, say, a hyperlink on each item, it would be easier. Start out by naming the root element in your XAML - e.g., for a Window:
<Window .... Name="This">
Now, in the DataTemplate for your ListBox items, use something like this:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<Hyperlink
Command="{Binding ElementName=This, Path=DataContext.OpenEntryCmd}"
Text="{Binding Path=Name}"
/>
The ElementName binding lets you resolve the OpenEntryCmd from the context of your ViewModel, rather than the specific data item.
EDIT: I wrote this post as an inexperienced WPF developer, nowadays I'd either use a framework that provides event to command binding, or simply use a button and restyle it. Of course for maximum flexibility this is maybe better.
I find the best way to do this is to create a simple user control wrapper for my content, with dependency properties for the command and parameter.
The reason I did this was due to the Button not bubbling the click event to my ListBox which prevented it from selecting the ListBoxItem.
CommandControl.xaml.cs:
public partial class CommandControl : UserControl
{
public CommandControl()
{
MouseLeftButtonDown += OnMouseLeftButtonDown;
InitializeComponent();
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs mouseButtonEventArgs)
{
if (Command != null)
{
if (Command.CanExecute(CommandParameter))
{
Command.Execute(CommandParameter);
}
}
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object),
typeof(CommandControl),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
CommandControl.xaml:
<UserControl x:Class="WpfApp.UserControls.CommandControl"
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="300" d:DesignWidth="300"
Background="Transparent">
</UserControl>
Usage:
<ListBoxItem>
<uc:CommandControl Command="{Binding LoadPageCommand}"
CommandParameter="{Binding HomePageViewModel}">
<TextBlock Text="Home" Margin="0,0,0,5" VerticalAlignment="Center"
Foreground="White" FontSize="24" />
</uc:CommandControl>
</ListBoxItem>
The Content can be whatever, and when the control is clicked, it will execute the command.
EDIT: Added Background="Transparent" to UserControl to enable click events on the entire area of the control.
This is a bit of a hack, but it works well and allows you to use commands and avoid code behind. This also has the added benefit of not triggering the command when you double-click (or whatever your trigger is) in the empty ScrollView area assuming your ListBoxItems don't fill the entire container.
Basically, just create a DataTemplate for your ListBox that is composed of a TextBlock and bind the width of the TextBlock to the width of the ListBox, set the margins and padding to 0, and disable horizontal scrolling (because the TextBlock will bleed beyond the visible bounds of the ScrollView triggering the horizontal scroll bar otherwise). The only bug I've found is that the command won't fire if the user clicks precisely on the border of the ListBoxItem, which I can live with.
Here is an example:
<ListBox
x:Name="listBox"
Width="400"
Height="150"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemsSource="{Binding ItemsSourceProperty}"
SelectedItem="{Binding SelectedItemProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Padding="0"
Margin="0"
Text="{Binding DisplayTextProperty}"
Width="{Binding ElementName=listBox, Path=Width}">
<TextBlock.InputBindings>
<MouseBinding
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBox}}, Path=DataContext.SelectProjectCommand}"
Gesture="LeftDoubleClick" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I recently needed to trigger an ICommand upon double clicking a ListBoxItem as well.
Personally, I don't like the DataTemplate method as it is binding to the content inside the ListBoxItem container, and not the container itself. I've opted to use an Attached Property to assign an InputBinding on the container. It takes a little more elbow grease, but it works well.
First, we need to create an attached property class. I've created mine a little more generically towards any class that derives from FrameworkElement, just in case I run into this again with a different visual.
public class FrameworkElementAttachedProperties : DependencyObject
{
public static readonly DependencyProperty DoubleClickProperty = DependencyProperty.RegisterAttached("DoubleClick", typeof(InputBinding),
typeof(FrameworkElementAttachedProperties), new PropertyMetadata(null, OnDoubleClickChanged));
public static void SetDoubleClick(FrameworkElement element, InputBinding value)
{
element.SetValue(DoubleClickProperty, value);
}
public static InputBinding GetDoubleClick(FrameworkElement element)
{
return (InputBinding)element.GetValue(DoubleClickProperty);
}
private static void OnDoubleClickChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = obj as FrameworkElement;
/// Potentially throw an exception if an object is not a FrameworkElement (is null).
if(e.NewValue != null)
{
element.InputBindings.Add(e.NewValue as InputBinding);
}
if(e.OldValue != null)
{
element.InputBindings.Remove(e.OldValue as InputBinding);
}
}
}
Then the final step is to override the base container style for the ListBoxItem.
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}"
BasedOn="{StaticResource ListBoxItem}">
<Setter Property="local:FrameworkElementAttachedProperties.DoubleClick">
<Setter.Value>
<MouseBinding Command="{Binding OnListBoxItemDoubleClickCommand}"
MouseAction="LeftDoubleClick"/>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
Now, anytime a ListBoxItem is double clicked, it will fire our OnListBoxItemDoubleClickCommand.
If you're looking for a nice simple solution that uses interactions instead of mucking about with user controls, code behind, input bindings, custom attached properties, etc.
And you want something that works at the ListBoxItem level, i.e. not ListBox level as per the (incorrectly) accepted solution.
Then here's a snippet for a simple 'button like' click action..
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Background="Transparent">
<!-- insert your visuals here -->
<b:Interaction.Triggers>
<b:EventTrigger EventName="MouseUp">
<b:InvokeCommandAction Command="{Binding YourCommand}" />
</b:EventTrigger>
</b:Interaction.Triggers>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note, background="Transparent" is required to ensure the entire Grid is clickable and not just the contents inside.