Cannot hit the getter of Property - c#

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.

Related

Use Commands with RadRibbonView Buttons in C# and not XAML

I am trying to get MVVM (pretty new to it) and RadRibbonView to work together. Problem is, I was working with a standard WPF application with an MVVM model.
In the XAML file I previously had.
<Menu>
<MenuItem Header="{x:Static properties:Resources.FileMenu}">
<MenuItem Header="{x:Static properties:Resources.FileMenuNewWorkflow}" Command="{Binding Path=NewWorkflowCommand}" InputGestureText="Ctrl+N"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuNewService}" Command="{Binding Path=NewServiceCommand}" InputGestureText="Shift+Ctrl+N"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuOpen}" Command="{Binding Path=OpenWorkflowCommand}" InputGestureText="Ctrl+O"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSave}" Command="{Binding Path=SaveWorkflowCommand}" InputGestureText="Ctrl+S"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSaveAs}" Command="{Binding Path=SaveAsWorkflowCommand}"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuSaveAll}" Command="{Binding Path=SaveAllWorkflowsCommand}" InputGestureText="Shift+Ctrl+S"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuAddReference}" Command="{Binding Path=AddReferenceCommand}"/>
<Separator/>
<MenuItem Header="{x:Static properties:Resources.FileMenuClose}" Command="{Binding Path=CloseWorkflowCommand}"/>
<MenuItem Header="{x:Static properties:Resources.FileMenuCloseAll}" Command="{Binding Path=CloseAllWorkflowsCommand}"/>
<Separator/>
</Menu>
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
As you can see the Command id was well defined. I wanted to add Ribbon to this and selected RibbonView from telerik WPF UI. Picked the Paint with MVVM model.
Converted the Menu to Ribbon
<telerik:RadRibbonView Grid.Row="0" x:Name="ribbonView" ApplicationName="MyApp" ItemsSource="{Binding Tabs}"
ApplicationButtonContent="File" Title="{x:Static properties:Resources.RibbonViewTitle}" ItemTemplate="{StaticResource TabTemplate}"
SelectedItem="{Binding SelectedTab, Mode=TwoWay}"
MinimizeButtonVisibility="Visible" HelpButtonVisibility="Visible">
In the MainWindowModelView The following is defined. SplitButtonViewModel inherits from ButtonViewModel.
private GroupViewModel GetFilesGroup()
{
GroupViewModel fileItems = new GroupViewModel();
fileItems.Text = "File";
SplitButtonViewModel newFile = new SplitButtonViewModel();
newFile.Text = "New";
newFile.Size = ButtonSize.Large;
newFile.LargeImage = GetPath("MVVM/new.png");
fileItems.Buttons.Add(newFile);
SplitButtonViewModel openFile = new SplitButtonViewModel();
openFile.Text = "Open";
openFile.Size = ButtonSize.Large;
openFile.LargeImage = GetPath("MVVM/open.png");
fileItems.Buttons.Add(openFile);
ButtonGroupViewModel buttonsGroup = new ButtonGroupViewModel();
buttonsGroup.Buttons.Add(GetButton("save", "Save"));
buttonsGroup.Buttons.Add(GetButton("SaveAll", "Save All"));
buttonsGroup.Buttons.Add(GetButton("SaveAs", "Save As"));
fileItems.Buttons.Add(buttonsGroup);
return fileItems;
}
My current ButtonViewModel from the telerik MVVM Ribbon example is
public class ButtonViewModel : ViewModelBase
{
private String text;
private ButtonSize size;
private string smallImage;
private string largeImage;
//perhaps work something with this...
private RelayCommand command;
/// <summary>
/// Gets or sets Text.
/// </summary>
public String Text
{
get
{
return this.text;
}
set
{
if (this.text != value)
{
this.text = value;
this.OnPropertyChanged("Text");
}
}
}
public ButtonSize Size
{
get
{
return size;
}
set
{
size = value;
}
}
public string SmallImage
{
get
{
return smallImage;
}
set
{
smallImage = value;
}
}
public string LargeImage
{
get
{
return largeImage;
}
set
{
largeImage = value;
}
}
}
So all the creation of the Groups is in the ModelView. The problem is I am not sure how to get the Command working. I have a RelayCommand class which takes care of the commands.
From the following link: http://docs.telerik.com/devtools/wpf/controls/radribbonview/how-to/howto-use-commands-with-radribbonview-buttons
Is it possible to just call the class RelayCommand (basically same as the example from the link), so I do not have to change much and just invoke the command from the above function? Something like openFile.Execute() or something.
Most of the questions were how to connect XAML to Commands. All the menu items are now in C# and wanted a command definition there.
Any help is appreciated.

WPF TreeView context menu is disabled if no items exist

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
});
}

WPF Button Command not firing ICommand in ViewModel

I have a View with a button as follows:
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
Command="{Binding DataContext.CmdTestButtonClicked}"
CommandParameter="{Binding}" />
In the view's code-behind, I set the DataContext to the ViewModel:
public GlobalSettings()
{
InitializeComponent();
...
DataContext = Helpers.IoCHelper.GlobalSettingsVM;
...
}
My ViewModel derives from a base class which exposes the ICommand:
public class GlobalSettingsVM : CollectionViewModel<GlobalSettings> { ... }
public abstract class CollectionViewModel<TModel> : IInstallModuleViewModel, INotifyPropertyChanged,
INotifyDataErrorInfo where TModel : Model, new()
{
...
public ICommand CmdTestButtonClicked
{
get
{
return _testButtonClicked ??
(_testButtonClicked = new RelayCommand(TestButtonClicked));
}
}
protected virtual void TestButtonClicked(object o)
{
// I never get here
}
}
I don't have any other issues using this pattern throughout my application, however all my other implementations have the Button within a ListView, so there I have to use RelativeSource={RelativeSource AncestorType={x:Type ListView}}.
Why would this command never fire? Do I need to set a RelativeSource here as well?
This
Command="{Binding DataContext.CmdTestButtonClicked}"
Implies that the Command will look for a property called DataContext in the object to which the button is bound.
If the DataContext of the button is a GlobalSettingsVM this should work:
Command="{Binding CmdTestButtonClicked}"
You could also use the MVVM Light toolkit wich is very convenient and helping on these situations.
You would get Something like this :
<Button Grid.Column="2" Grid.Row="1" Content="Test" Margin="10,4"
<i:Interaction.Triggers>
<i:EventTrigger EventName="OnClick" >
<Command:EventToCommand Command="{Binding DataContext.CmdTestButtonClicked}" CommandParameter="{Binding}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In my case, I was listening to PreviewMouseLeftButtonDown under constructor of xaml.cs class which was stopping command event callback to the view model.
this.PreviewMouseLeftButtonDown += (s, e) => DragMove();
Instead of above line, in xaml file for window added MouseLeftButtonDown="Window_MouseLeftButtonDown" click handler and handled window drag within it, as below
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}

Caliburn.Micro: Call method of property on DataContext

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.

Hooking-up commands in AvalonEdit used in ListView's ItemTemplate doesn't work

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!

Categories

Resources