How to use ApplicationCommands in a MVVM AvalonDock Program? - c#

I'm working on a program that use AvalonDock to open several documents at the same time. And there is a public ribbon which has some common buttons on the top, like this:
Now, Cut, Copy, Paste, Delete can be used by clicking MenuItems of them in ContextMenu.
However, there are some problems when I want to bind these commands to buttons in the ribbon.
Imitating the example of AvalonDock, my XAML is
<DockingManager DocumentsSource="{Binding Documents}" ActiveContent="{Binding ActiveDocument,Mode=TwoWay}">
<DockingManager.LayoutItemTemplateSelector>
<local:PanesTemplateSelector>
<local:PanesTemplateSelector.ShapesDocumentTemplate>
<DataTemplate>
<view:ShapesDocument/>
</DataTemplate>
</local:PanesTemplateSelector.ShapesDocumentTemplate>
</local:PanesTemplateSelector>
</DockingManager.LayoutItemTemplateSelector>
</DockingManager>
And my C# is:
public ObservableCollection<ShapesDocumentViewModel> Documents { get; set; } = new ObservableCollection<ShapesDocumentViewModel>();
So the ActiveDocument is a ViewModel.
In my ShapesDocument, there is a CanvasEx with Cut_Executed,Copy_Executed...
So, how can I bind the CommandTarget? Or there will be some ways to move the Executeds into ViewModel?
<Button Command="{x:Static ApplicationCommands.Cut}" CommandTarget="{Binding ???}"/>

Temporarily, I use MenuItems to replace the buttons. I set the ApplicationCommands in the Control in ShapeDocument UserControl, and binding them to MenuItems without setting the command targets.
I guess that WPF can automaticly search the target of menuitems, but not button. So I can change them to MenuItems, and change the MenuItems' style to imitate buttons.
It's not very elegant but very convinent, with very simple codes.

Related

TreeViewItem Command binding

I need to click on an TreeViewItem and open an dialog window with the data of that TreeViewItem, later based on that data I will run another command.
My actual problem is: I can't click on it because treeviewitem doesn't have the command property.
My scenario: I have 2 Models with 2 properties that will be used to create my TreeViewItems. On my ViewModel I create them, and organize them inside each other based on their properties and then store them inside One Collection.
Here's my xaml to bind the elements:
<TreeView ItemsSource="{Binding Local}">
<TreeView.DataContext>
<data:ItemViewModel/>
</TreeView.DataContext>
</Treeview>
//In my "Local" property I have 3 TreeViewItems with other items inside them which I want to execute the commands
I couldn't find a way to create a datatemplate for that specific scenario. Even tried to create a datatemplate with a Hyperlink (thought it would be a temporary solution) inside it, but would not execute any command.
MVVM pattern is to use one of the many "Event to Command" implementations out there. You basically bind the "Event to Command" object to the Click event and then a command in your VM gets bound to the "Event to Command" object and it gets mapped behind the scenes for you and handles all the enabled / disabled stuff.
You can see an example of one of the implementations here:
WPF Binding UI events to commands in ViewModel
You should be binding to a collection whose objects have a collection as a public property and templating by type into whatever you want to see in each treeviewitem.
Like this sample:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.hierarchicaldatatemplate?view=netframework-4.7.2
Technically, you could have a button whose template was a textblock or something and that would then have the behaviour of a button such as click and command.
But I'd be more likely to use an inputbinding.
Here's an example:
<DataTemplate DataType="{x:Type local:LineTerrainVM}">
<Grid Background="Transparent">
<Grid.InputBindings>
<MouseBinding MouseAction="RightClick" Command="{Binding FixLineCommand}"/>
</Grid.InputBindings>
You can give that a commandparameter="{Binding .}" and it'll pass the viewmodel as a parameter.
You could also use relativesource to the datacontext of the treeview to get at a parent viewmodel and define a command in that to do your stuff.
Since that stuff you want to do is a view responsibility you could rely on routed events without "breaking" mvvm. A click in any treeviewitem could be handled at the treeview level and use the originalsource to get to the treeviewitem clicked. Then grab it's datacontext for the viewmodel of whatever that is.
Rough idea:
<TreeView Name="tv" ItemsSource="{Binding Families}" FrameworkElement.PreviewMouseLeftButtonDown="Tv_PreviewMouseLeftButtonDown"
And the handler:
private void Tv_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var vm = ((FrameworkElement)e.OriginalSource).DataContext;
// do something with viewmodel
}
You could then do something like new up your dialog window, set it's datacontext to that viewmodel you just got and showdialog it.

How to handle Click event on a custom WPF control composed of controls that don't have Click events

I am creating a custom ToolBar control that I will be able to use in my WPF application.
My CustomToolBar control is based on a StackPanel control and will contain multiple CustomToolBarButton controls.
My CustomToolBarButtons are based on a vertical StackPanel and contain an Image control and a Label control.
I want to programmatically create a Click event for my CustomToolBarButtons that will fire when Image or label gets clicked. Unfortunately, neither Image nor Label controls have Click events in WPF.
It's a surprise for me because I am used to WinForms controls and vast majority of them have Click events by default. Do I have to create custom Image and Label controls and create Click events for them or is there a cleaner and simpler way of doing this?
Thanks for any help!
Well, there's not a great and simple way using standard functionality. I got around this by making my own trigger (derived from System.Windows.Interactivity.TriggerBase from the Blend SDK) so that in my projects I can do the following:
<Label>
<i:Interaction.Triggers>
<mu:MouseTrigger MouseButton="Middle" MouseAction="Click">
<i:InvokeCommandAction Command="{Binding Path=Close}" />
</mu:MouseTrigger>
</i:Interaction.Triggers>
</Label>
Effectively the MouseTrigger class will handle MouseDown and MouseUp events from the UIElement it's attached to and use that to invoke the actions associated with the trigger. The code is a bit more complex than just that, though, since I also do mouse capturing & I utilize an internal helper class so that I could add multiple triggers to the same element with only one instance of a helper class handling events & capturing the mouse for that element.
For the actual actions, I just use existing Blend or Prism actions such as InvokeCommandAction.
Here is the project if you're interested. It was too large to paste into this format. It uses some C# 6.0 features but you could easily modify it to work on an older version of C# by removing some null conditional operators. It requires you install the Blend SDK as it depends on System.Windows.Interactivity (should install with Visual Studio as long as you select that option). MouseTrigger is the publicly visible class that is the point of interaction with the functionality. MouseCatcher is the internal helper class mentioned.
I'd recommend not going down the route of custom controls, but rather of using some mechanism (this one or otherwise) to extend the existing functionality using the attached property framework that WPF and XAML bring to the table.
In WinForms you would create custom controls simply to get a nonstandard look, in WPF this is no longer necessary.
The WPF way: instead of making a Stackpanel's children implement Click behaviour, let's make a Button look like the desired Stackpanel.
Could do this setting the Content:
<Button>
<StackPanel >
<Image Source="C:\myFiles\myPic.png"/>
<Label HorizontalAlignment="Center">SomeTxt</Label>
</StackPanel>
</Button>
But this way it still looks like a Button, to overcome this we can set the Template instead:
<Button>
<Button.Template>
<ControlTemplate TargetType="Button">
<StackPanel >
<Image Source="C:\myFiles\myPic.png"/>
<Label HorizontalAlignment="Center">SomeTxt</Label>
</StackPanel>
</ControlTemplate>
</Button.Template>
</Button>
Both solutions will raise a Click event clicking the image or label.

WPF: Which solution? TabControl with close button and new tab button

I'm trying to find the best solution for a TabControl that both support a close button on each TabItem, and always show a "new tab button" as the last tab.
I've found some half working solutions, but i think that was for MVVM, that I'm not using. Enough to try to understand WPF =)
This is the best solution I've found so far:
http://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF
A solution that i actually understand. But the problem is that it is using the ItemsSource, and i don't want that. I want to bind the ItemsSource to my own collection without having to have special things in that collection to handle the new tab button.
I've been search for days now but cant find a good solution.
And I'm really new to WPF, otherwise i could probably have adapted the half done solutions I've found, or make them complete. But unfortunately that is way out of my league for now.
Any help appreciated.
I have an open source library which supports MVVM and allows extra content, such as a button to be added into the tab strip. It is sports Chrome style tabs which can tear off.
http://dragablz.net
This is bit of a dirty way to achieve the Add (+) button placed next to the last TabItem without much work. You already know how to place a Delete button next to the TabItem caption so I've not included that logic here.
Basically the logic in this solution is
To bind ItemsSource property to your own collection as well as
the Add TabItem using a CompositeCollection.
Disable selection of
the Add(+) TabItem and instead perform an action to load a new tab when it
is clicked/selected.
XAML bit
<TextBlock x:Name="HiddenItemWithDataContext" Visibility="Collapsed" />
<TabControl x:Name="Tab1" SelectionChanged="Tab1_SelectionChanged" >
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.MyList, Source={x:Reference HiddenItemWithDataContext}}" />
<TabItem Height="0" Width="0" />
<TabItem Header="+" x:Name="AddTabButton"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
The code behind
private void Tab1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(AddTabButton))
{
//Logic for adding a new item to the bound collection goes here.
string newItem = "Item " + (MyList.Count + 1);
MyList.Add(newItem);
e.Handled = true;
Dispatcher.BeginInvoke(new Action(() => Tab1.SelectedItem = newItem));
}
}
You could make a converter which appends the Add tab. This way the collection of tabs in you viewmodel will only contain the real tabs.
The problem is then how to know when the Add tab is selected. You could make a TabItem behavior which executes a command when the tab is selected. Incidentally I recommended this for another question just recently, so you can take the code from there: TabItem selected behavior
While I don't actually have the coded solution, I can give some insight on what is most likely the appropriate way to handle this in a WPF/MVVM pattern.
Firstly, if we break down the request it is as follows:
You have a sequence of elements that you want to display.
You want the user to be able to remove an individual element from the sequence.
You want the user to be able to add a new element to the sequence.
Additionally, since you are attempting to use a TabControl, you are also looking to get the behavior that a Selector control provides (element selection), as well as an area to display the element (content) which is selected.
So, if we stick to these behaviors you'll be fine, since the user interface controls can be customized in terms of look and feel.
Of course, the best control for this is the TabControl, which are you already trying to use. If we use this control, it satisfies the first item.
<TabControl ItemsSource="{Binding Path=Customers}" />
Afterwards, you can customize each element, in your case you want to add a Button to each element which will execute a command to remove that element from the sequence. This will satisfy the second item.
<TabControl ...>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CustomerId}" />
<Button Command="{Binding Path=RemoveItemCommand, Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
<TabControl.ItemTemplate>
</TabControl>
The last part is a bit more difficult, and will require you to actually have to create a custom control that inherits from the TabControl class, add an ICommand DependencyProperty, and customize the control template so that it not only displays the TabPanel, but right next to it also displays a Button which handles the DependencyProperty you just created (the look and feel of the button will have to be customized as well). Doing all of this will allow you to display your own version of a TabControl which has a faux TabItem, which of course is your "Add" button. This is far far far easier said than done, and I wish you luck. Just remember that the TabPanel wraps onto multiple rows and can go both horizontally or vertically. Basically, this last part is not easy at all.

Expanding grid (or similar) in WPF for a modeling program

I am pretty new to WPF, and in order to get some knowledge I decided to make a very simple UML modeling program, that basically offers the possibility to put some classes onto a canvas, connect them and move them around.
Now to the question:
I have been thinking about letting the classes I put on the canvas being a userControl I design. In my mind it would be something like a Grid, with some textboxes to represent properties, attributes and so on. The actual question is then, is my idea possible, or should I go with something completely different? My concern right now is how to implement the grid such that it can expand (add a row) under the right heading (Attribute/property..) when I want it to, and not be expanded to a maximum from the beginning.
I hope you can understand my question, and give me an idea to whether I should continue to implement it how I thought about, or do it using some other method.
You may wish to consider a ListView control, perhaps with an Expander, something like this:
<Canvas>
<Expander Header="Stuff"
MaxHeight="900"
Canvas.Left="202"
Canvas.Top="110">
<ListView Name="MyListView">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add new thing"
Click="MenuItem_Click" />
</ContextMenu>
</ListView.ContextMenu>
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Label>Name</Label>
<TextBox Text="Value" />
</StackPanel>
</ListViewItem>
<ListViewItem>Item two</ListViewItem>
<ListViewItem>Item three</ListViewItem>
</ListView>
</Expander>
</Canvas>
This will size as needed up to the max given. The list view items could contain any sort of content (not just text) as you can see above. You will want to learn a bit about Style and Control templates. WPF has IMHO a rather steep learning curve but there are a lot of learning resources on the web. Good luck.
In response to your comment, I'm adding additional information.
Anything you can do in XAML you can do in code behind (mostly XAML just calls framework objects). In this case I've added a context menu to the ListView control. This menu contains one item "Add new thing". There is a Click event for this item which is bound to the MenuItem_Click method in the code behind. I then added this method to the code:
void MenuItem_Click(object sender, RoutedEventArgs e) {
var lvi = new ListViewItem();
lvi.Content = String.Format("New thing {0}", DateTime.Now);
MyListView.Items.Add(lvi);
}
Now if you right click in the ListView you will see the "Add new thing" menu selection, left clicking it adds a new ListViewItem into the ListView (programmatically).

ContextMenu won't go away after selection.

I have the following code that adds a context menu to a textbox on the UI. The user is supposed to be able to bring up the context menu and select a new units to be used. So the method CurrentUnits in my view model is bound to the textbox. I want a context menu populated by all the potential units. So the method Units in my view model returns a string[] of unit options; such as inches, cm, feet, meters, etc. When the user selects one the method NewUnits_Click is invoked. All works fine, however the contextmenu does not go away when the user selects a menu option. Pressing somewhere else on the screen like the application menu bar will then clear it. Has anyone else seen this problem, or see something wrong with the code below. It seems to have something to do with the ItemTemplate/DataTemplate I have, as creating an set of menu items by hand works fine.
<TextBlock Width="100" Text="{Binding CurrentUnits}" TextAlignment="Right">
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu ItemsSource="{Binding Units}">
<toolkit:ContextMenu.ItemTemplate>
<DataTemplate>
<toolkit:MenuItem Header="{Binding}" Click="NewUnits_Click" />
</DataTemplate>
</toolkit:ContextMenu.ItemTemplate>
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
</TextBlock>
If you aren't using MVVM when working with WPF, I highly suggest you to start doing so. And even if you are not, I suggest that instead of binding to Click you instead bind on the Command property which wants something that implements ICommand. I think that the behavior you are getting is intended, buttons and menu items in WPF are intended to bind to Commands, it's not just WinForms 2.0.
Another solution would be to hide the Context menu in the code-behind. Perhaps this resource will help you in achieving that.

Categories

Resources