I have an user control (ex: UserCtrlClass)with a tree view inside it
I have View model (ex: OBJViewModel) class for represent the actual items/data display on the tree view
Next I have a Tree View Model (ex: TreeViewModel), which has a list of OBJViewModel objects
Now in the code behind file of the user control, I have instantiated the tree view model class and set as the data context of the user control class
I need a context sensitive menu, which i need to display only when I right click on a specific item in the tree, so I have handled the right click event of the user control class and did the work there
But the commands are not working, The commands are derived from I command and instantiated in TreeViewModel class. i tried to debug my Command.execute was never hit! Any help would be appreciated as I am being a newbie to .net and wpf
TreeViewModel class
<UserControl Name="PFDBUserCtrl" x:Class="BFSimMaster.BFSMTreeview"
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"
xmlns:local="clr-namespace:BFSimMaster.ViewModel"
xmlns:cmd="clr-namespace:BFSimMaster.Commands"
mc:Ignorable="d"
d:DesignHeight="66" d:DesignWidth="300">
<UserControl.Resources>
<!--cmd:ActivateProjectCmd x:Key="CMDActivateProject"/-->
<!--cmd:DeActivateProjectCmd x:Key="CMDDeActivateProject"/-->
</UserControl.Resources>
<DockPanel>
<!-- PF Object Browser TREE -->
<TreeView Name="PFDataBrowser" ItemsSource="{Binding LevelOnePFObjects}" >
<TreeView.Resources>
<ContextMenu x:Key ="ProjectMenu" StaysOpen="true" >
<!-- Text="{Binding Source={StaticResource myDataSource}, Path=PersonName}-->
<!--MenuItem Header="Activate" Command="{Binding Source={StaticResource CMDActivateProject}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/-->
<MenuItem Header="Activate" Command="{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<MenuItem Header="Deactivate" Command="{Binding Source=TVViewModel, Path=CMDDeActivateProject}" CommandParameter="{Binding Path=PlacementTarget,RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<!-- This Style binds a TreeViewItem to a PFObject View Model.-->
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseRightButtonDown" Handler="OnRightButtonDown"/>
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
Code behind class
using System;
namespace BFSimMaster
{
public partial class BFSMTreeview : UserControl
{
readonly TreeViewItemViewModel mViewModelPFObjBrowserTree;
public BFSMTreeview()
{
InitializeComponent();
WApplication appPF = PFAPIUtils.APIInstance.GetApplication();
WDataObject User = appPF.GetCurrentUser();
// Get raw objects - tree data from a PF database.
//BFPFDataObject userdb = new BFPFDataObject(User,false,"*.IntPrj");
BFPFDataObject userdb = new BFPFDataObject(User, true);
// Create UI-friendly wrappers around the
// raw data objects (i.e. the view-model).
mViewModelPFObjBrowserTree = new TreeViewItemViewModel(userdb);
// Let the UI bind to the view-model.
base.DataContext = mViewModelPFObjBrowserTree;
}
public TreeViewItemViewModel TVViewModel
{
get { return mViewModelPFObjBrowserTree; }
}
private void OnRightButtonDown(object sender, MouseButtonEventArgs e)
{
//MessageBox.Show("Right Clicked on tree view");
if (sender is TreeViewItem)
{
e.Handled = true;
(sender as TreeViewItem).IsSelected = true;
string strObjectType = ((sender as TreeViewItem).Header as PFObjectViewModel).PFDataObject.mThisPFObject.GetClassName().GetString();
switch (strObjectType)
{
case "IntPrj":
(sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
(sender as TreeViewItem).ContextMenu.PlacementTarget = (sender as TreeViewItem);
break;
case "Folder":
(sender as TreeViewItem).ContextMenu = PFDataBrowser.Resources["ProjectMenu"] as System.Windows.Controls.ContextMenu;
break;
}
}
}
}
}
the TreeViewModel Class
using System;
namespace BFSimMaster.ViewModel
{
public class TreeViewItemViewModel
{
#region Data
readonly ReadOnlyCollection<PFObjectViewModel> mLevelOnePFObjects;
readonly PFObjectViewModel mRootOfPFObjects;
#endregion // Data
#region Constructor
public TreeViewItemViewModel(BFPFDataObject rootOfPFObjectsA)
{
this.CMDActivateProject = new ActivateProjectCmd();
this.CMDDeActivateProject = new DeActivateProjectCmd();
mRootOfPFObjects = new PFObjectViewModel(rootOfPFObjectsA);
mLevelOnePFObjects = new ReadOnlyCollection<PFObjectViewModel>(
new PFObjectViewModel[]
{
mRootOfPFObjects
});
}
#endregion // Constructor
public ICommand CMDActivateProject { get; set; }
public ICommand CMDDeActivateProject { get; set; }
public ReadOnlyCollection<PFObjectViewModel> LevelOnePFObjects
{
get { return mLevelOnePFObjects; }
}
}
}
A ContextMenu isn't part of the logical tree, so this binding "{Binding DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}" wont work, because it simply has no ancestor of type UserControl. If you dont set the DataContext of the PlacementTarget manuelly, you could try
"{Binding PlacementTarget.DataContext.CMDActivateProject, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}"
I not 100% sure about the following, but I think this will just work, if youre using the ContextMenu property:
<Treeview>
<Treeview.ContextMenu>
<ContextMenu>
...
<ContextMenu>
<Treeview.ContextMenu>
...
The adventage here is, that you dont have to handle the rightbuttondown event in code-behind, it automatically opens if you right click on your treeview.
I've solved this problem by introducing VM class for MenuItem and setting context menu with ExtendedContextMenu.Items={Binding ContextMenu} attached property. MenuResourcesDictionary is ResourceDictionary.xaml with back-side .cs file (shown below).
To use it for your code you need to add IEnumerable<MenuItemVM> ContextMenu property on your tree model and put commands there (e.g. pass them to MenuItemVM constructor). And in the item style add <Setter Property="ExtendedContextMenu.Items" Value="{Binding DataContext.ContextMenu}" />
public static class ExtendedContextMenu
{
private static readonly StyleSelector _styleSelector = new ContextMenuItemStyleSelector();
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.RegisterAttached("Items",
typeof(IEnumerable<MenuItemVM>),
typeof(ExtendedContextMenu),
new FrameworkPropertyMetadata(default(IEnumerable<MenuItemVM>), ItemsChanged));
public static void SetItems(DependencyObject element, IEnumerable<MenuItemVM> value)
{
element.SetValue(ItemsProperty, value);
}
public static IEnumerable<MenuItemVM> GetItems(DependencyObject element)
{
return (IEnumerable<MenuItemVM>)element.GetValue(ItemsProperty);
}
private static void ItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (FrameworkElement)d;
var items = (IEnumerable<MenuItemVM>)e.NewValue;
var contextMenu = new ContextMenu();
contextMenu.ItemContainerStyleSelector = _styleSelector;
contextMenu.ItemsSource = items;
return contextMenu;
}
private static void AdjustContextMenuVisibility(ContextMenu menu)
{
menu.Visibility = menu.HasItems ? Visibility.Visible : Visibility.Hidden;
}
}
public class ContextMenuItemStyleSelector : StyleSelector
{
private static readonly MenuResourcesDictionary _resources = new MenuResourcesDictionary();
private static readonly Style _menuItemStyle = (Style)_resources[MenuResourcesDictionary.MenuItemStyleResourceKey];
private static readonly Style _separatorStyle = (Style)_resources[MenuResourcesDictionary.SeparatorStyleResourceKey];
public override Style SelectStyle(object item, DependencyObject container)
{
if (item == MenuItemVM.Separator)
return _separatorStyle;
return _menuItemStyle;
}
}
public sealed partial class MenuResourcesDictionary
{
public const string MenuItemStyleResourceKey = "MenuItemStyleResourceKey";
public const string DynamicMenuItemStyleResourceKey = "DynamicMenuItemStyleResourceKey";
public const string SeparatorStyleResourceKey = "SeparatorStyleResourceKey";
public const string LoadingStyleResourceKey = "LoadingMenuItemStyleResourceKey";
public MenuResourcesDictionary()
{
InitializeComponent();
}
}
Here is XAML styles:
<Style x:Key="{x:Static local:MenuResourcesDictionary.MenuItemStyleResourceKey}">
<Setter Property="MenuItem.Header" Value="{Binding Path=(menuVMs:MenuItemVM.Text)}" />
<Setter Property="MenuItem.Command" Value="{Binding Path=(menuVMs:MenuItemVM.Command)}" />
</Style>
<Style x:Key="{x:Static local:MenuResourcesDictionary.SeparatorStyleResourceKey}" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MenuItemVM class is more-or-less straighforward, so I don't write it here.
I Found the Answer for TreeView Context menu with and with out DataTemplate here:
TreeView ContextMenu MVVM Binding
MVVM binding command to contextmenu item
Related
Good day.
I have created a TreeViewHelper Class in order to trigger events and pass through datacontext to functions. However. Id like to select the treeviewitem when right clicking.
My solution follows the MVVM pattern as close as possible. So if possible i'd like to avoid the Trigger Event feature that is provided.
Id love for some advice on how to tackle this issue.
here is the treeviewhelper.cs class
public static class TreeViewHelper
{
#region SelectedItem
public static object GetSelectedItem(DependencyObject obj)
{
return (object)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItem(DependencyObject obj, object value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, SelectedItemChanged));
private static void SelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs dpea)
{
if (!(obj is TreeView) || dpea.NewValue == null)
return;
var view = obj as TreeView;
view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue);
var command = (ICommand)(view as DependencyObject).GetValue(SelectedItemChangedProperty);
if (command != null)
{
if (command.CanExecute(null))
command.Execute(new DependencyPropertyEventArgs(dpea));
}
}
#endregion
#region Selected Item Changed
public static ICommand GetSelectedItemChanged(DependencyObject obj)
{
return (ICommand)obj.GetValue(SelectedItemProperty);
}
public static void SetSelectedItemChanged(DependencyObject obj, ICommand value)
{
obj.SetValue(SelectedItemProperty, value);
}
public static readonly DependencyProperty SelectedItemChangedProperty =
DependencyProperty.RegisterAttached("SelectedItemChanged", typeof(ICommand), typeof(TreeViewHelper));
#endregion
#region Event Args
public class DependencyPropertyEventArgs : EventArgs
{
public DependencyPropertyChangedEventArgs DependencyPropertyChangedEventArgs { get; private set; }
public DependencyPropertyEventArgs(DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
this.DependencyPropertyChangedEventArgs = dependencyPropertyChangedEventArgs;
}
}
#endregion
}
Here is the XAML Code (shortened)
<TreeView x:Name="TestPlanTreeView"
ItemsSource="{Binding Data.TreeViewCollection}"
utils:TreeViewHelper.SelectedItem="{Binding CurrSelItem}"
utils:TreeViewHelper.SelectedItemChanged="{Binding SelItemChgCmd}">
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding MenuCollection, RelativeSource={RelativeSource Self}}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<TextBlock Text="{Binding Header}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
This is how i trigger a function when selecting a new item.
SelItemChgCmd = new RelayCommand<DependencyPropertyEventArgs>(Data.TreeViewItemSelected);
CurrSelItem = new object();
public void TreeViewItemSelected(DependencyPropertyEventArgs dpe)
{
TreeViewItem selectedTreeViewItem = dpe.DependencyPropertyChangedEventArgs.NewValue as TreeViewItem;
ListViewCollection = BuildListViewCollection(selectedTreeViewItem);
}
What I would love to do, is to be able to select the treeviewitem before right clicking on the object. However, I am not sure how to do that with my given code without hacking it.
My original thoughts were to create a property in the treeviewhelper for PreviewRightMouseButtonUp and have that trigger the SelectedItem event, but i then run into issues with data.
Since the PreviewRightMouseButtonUp does not hold the same method types as the selectedItem Changed event, then i run into these data issues.
Id really appreciate some thoughts on this matter. Its been blocking me for a while now.
Given an arbitrary ItemsControl, like a ListView, I want to set a Binding from inside the ItemsTemplate to the hosting Container. How can I do that easily? For example, in WPF we can do it using this inside the ItemTemplate
<ListView.ItemTemplate>
<DataTemplate>
<SomeControl Property="{Binding Path=TargetProperty, RelativeSouce={RelativeSource FindAncestor, AncestorType={x:Type MyContainer}}}" />
</DataTemplate>
<ListView.ItemTemplate>
In this example (for WPF) the Binding will be set between Property in SomeControl and TargetProperty of the ListViewItem (implicit, because it will be generated dynamically by the ListView to host the each of its items).
How can we do achieve the same in UWP?
I want something that is MVVM-friendly. Maybe with attached properties or an Interaction Behavior.
When the selection changes, search the visual tree for the radio button with the DataContext corresponding to selected/deselected items. Once it's found, you can check/uncheck at your leisure.
I have a toy model object looking like this:
public class Data
{
public string Name { get; set; }
}
My Page is named self and contains this collection property:
public Data[] Data { get; set; } =
{
new Data { Name = "One" },
new Data { Name = "Two" },
new Data { Name = "Three" },
};
The list view, binding to the above collection:
<ListView
ItemsSource="{Binding Data, ElementName=self}"
SelectionChanged="OnSelectionChanged">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The SelectionChanged event handler:
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListView lv = sender as ListView;
var removed = FindRadioButtonWithDataContext(lv, e.RemovedItems.FirstOrDefault());
if (removed != null)
{
removed.IsChecked = false;
}
var added = FindRadioButtonWithDataContext(lv, e.AddedItems.FirstOrDefault());
if (added != null)
{
added.IsChecked = true;
}
}
Finding the radio button with a DataContext matching our Data instance:
public static RadioButton FindRadioButtonWithDataContext(
DependencyObject parent,
object data)
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
ListViewItem lv = child as ListViewItem;
if (lv != null)
{
RadioButton rb = FindVisualChild<RadioButton>(child);
if (rb?.DataContext == data)
{
return rb;
}
}
RadioButton childOfChild = FindRadioButtonWithDataContext(child, data);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return null;
}
And finally, a helper method to find a child of a specific type:
public static T FindVisualChild<T>(
DependencyObject parent)
where T : DependencyObject
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
return candidate;
}
T childOfChild = FindVisualChild<T>(child);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
The result:
This will break if a given model instance shows up more than once in the list.
Note: this answer is based on WPF, there might be some changes necessary for UWP.
There are basically two cases to consider:
You have a data driven aspect that needs to be bound to the item container
You have a view-only property
Lets assume a customized listview for both cases:
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
}
public class DesignerItem : ListViewItem
{
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem));
}
In case 1, you can use the ItemContainerStyle to link your viewmodel property with a binding and then bind the same property inside the datatemplate
class MyData
{
public bool IsEditing { get; set; } // also need to implement INotifyPropertyChanged here!
}
XAML:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<CheckBox IsChecked="{Binding IsEditing}"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
In case 2, it appears that you don't really have a data driven property and consequently, the effects of your property should be reflected within the control (ControlTemplate).
In the following example a toolbar is made visible based on the IsEditing property. A togglebutton can be used to control the property, the ItemTemplate is used as an inner element next to the toolbar and button, it knows nothing of the IsEditing state:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Setter Property="IsEditing" Value="{Binding IsEditing,Mode=TwoWay}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DesignerItem}">
<DockPanel>
<ToggleButton DockPanel.Dock="Right" Margin="5" VerticalAlignment="Top" IsChecked="{Binding IsEditing,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" Content="Edit"/>
<!--Toolbar is something control related, rather than data related-->
<ToolBar x:Name="MyToolBar" DockPanel.Dock="Top" Visibility="Collapsed">
<Button Content="Tool"/>
</ToolBar>
<ContentPresenter ContentSource="Content"/>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter TargetName="MyToolBar" Property="Visibility" Value="Visible"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
</local:MyListView>
For a better control template, you may chose to use Blend and create the control template starting at the full ListViewItem template and just editing your changes into it.
If your DesignerItem generally has a specific enhanced appearance, consider designing it in the Themes/Generic.xaml with the appropriate default style.
As commented, you could provide a separate data template for the editing mode. To do this, add a property to MyListView and to DesignerItem and use MyListView.PrepareContainerForItemOverride(...) to transfer the template.
In order to apply the template without the need for Setter.Value bindings, you can use value coercion on DesignerItem.ContentTemplate based on IsEditing.
public class MyListView : ListView
{
protected override DependencyObject GetContainerForItemOverride()
{
return new DesignerItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is DesignerItem;
}
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var elem = element as DesignerItem;
elem.ContentEditTemplate = ItemEditTemplate;
}
public DataTemplate ItemEditTemplate
{
get { return (DataTemplate)GetValue(ItemEditTemplateProperty); }
set { SetValue(ItemEditTemplateProperty, value); }
}
public static readonly DependencyProperty ItemEditTemplateProperty =
DependencyProperty.Register("ItemEditTemplate", typeof(DataTemplate), typeof(MyListView));
}
public class DesignerItem : ListViewItem
{
static DesignerItem()
{
ContentTemplateProperty.OverrideMetadata(typeof(DesignerItem), new FrameworkPropertyMetadata(
null, new CoerceValueCallback(CoerceContentTemplate)));
}
private static object CoerceContentTemplate(DependencyObject d, object baseValue)
{
var self = d as DesignerItem;
if (self != null && self.IsEditing)
{
return self.ContentEditTemplate;
}
return baseValue;
}
private static void OnIsEditingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
d.CoerceValue(ContentTemplateProperty);
}
public bool IsEditing
{
get { return (bool)GetValue(IsEditingProperty); }
set { SetValue(IsEditingProperty, value); }
}
public static readonly DependencyProperty IsEditingProperty =
DependencyProperty.Register("IsEditing", typeof(bool), typeof(DesignerItem), new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsEditingChanged)));
public DataTemplate ContentEditTemplate
{
get { return (DataTemplate)GetValue(ContentEditTemplateProperty); }
set { SetValue(ContentEditTemplateProperty, value); }
}
// Using a DependencyProperty as the backing store for ContentEditTemplate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentEditTemplateProperty =
DependencyProperty.Register("ContentEditTemplate", typeof(DataTemplate), typeof(DesignerItem));
}
Note, for an easier example I will activate the "edit" mode by ListViewItem.IsSelected with some trigger:
<local:MyListView ItemsSource="{Binding Source={StaticResource items}}">
<local:MyListView.ItemContainerStyle>
<Style TargetType="{x:Type local:DesignerItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="IsEditing" Value="True"/>
</Trigger>
</Style.Triggers>
</Style>
</local:MyListView.ItemContainerStyle>
<local:MyListView.ItemTemplate>
<DataTemplate>
<Border Background="Red" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemTemplate>
<local:MyListView.ItemEditTemplate>
<DataTemplate>
<Border Background="Green" Margin="5" Padding="5">
<TextBlock Text="Hello World"/>
</Border>
</DataTemplate>
</local:MyListView.ItemEditTemplate>
</local:MyListView>
Intended behavior: the selected item becomes edit-enabled, getting the local:MyListView.ItemEditTemplate (green) instead of the default template (red)
Just in case you might want to have an IsSelected property in your view model item class, you may create a derived ListView that establishes a Binding of its ListViewItems to the view model property:
public class MyListView : ListView
{
public string ItemIsSelectedPropertyName { get; set; } = "IsSelected";
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
BindingOperations.SetBinding(element,
ListViewItem.IsSelectedProperty,
new Binding
{
Path = new PropertyPath(ItemIsSelectedPropertyName),
Source = item,
Mode = BindingMode.TwoWay
});
}
}
You might now simply bind the RadioButton's IsChecked property in the ListView's ItemTemplate to the same view model property:
<local:MyListView ItemsSource="{Binding DataItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Content}"
IsChecked="{Binding IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</ListView.ItemTemplate>
</local:MyListView>
In the above example the data item class also has Content property. Obviously, the IsSelected property of the data item class must fire a PropertyChanged event.
I wrote custom TreeView control.
XAML:
<TreeView x:Class="EArchiveMaster.View.MyTreeView"
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">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<EventSetter Event="LostFocus" Handler="EventSetter_OnHandler" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
.cs
public partial class MyTreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
InitializeComponent();
}
private void EventSetter_OnHandler(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
But when I try to use it I got well known error:
Cannot set Name attribute value 'TextBox' on element 'TextBox'. 'TextBox' is under the scope of element 'MyTreeView', which already had a name registered when it was defined in another scope.
I found some receipts how to fix this error. Namely, specify .xaml part of control in its code-behind.
But I have no idea how can I do this.
The code clearly shows you want to extend TreeView. Basically if you want to build control that can hold some content(which can be named...), like ContentControl, ItemsControl, etc.. it is always better to go with CustomControl. UserControl with XAML and CS code is not suitable for this case.
In your case, create a class like below and extend the functionalities,
public class MyTreeView : TreeView
{
public event Action SomeItemLostFocus;
public MyTreeView()
{
DefaultStyleKey = typeof(MyTreeView);
}
public override void OnLostFocus(object sender, RoutedEventArgs e)
{
e.Handled = true;
if (SomeItemLostFocus != null)
SomeItemLostFocus();
}
}
If you want to customize the look and feel, you should override the default Style of the control. This style should be available in generic.xaml file inside Themes folder. More information on Custom Control development is here.
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
I found solution appropriate for me. This it the way how to define Style of TreeViewItem in code, not in XAML. Now I have TreeView definded only in code-behind, therefore, error will not be risen.
public class MyTreeView : TreeView
{
public event RoutedEventHandler ItemLostLogicFocus;
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
var itemContainerStyle = new Style
{
TargetType = typeof(TreeViewItem),
};
#region Binding
var expandedBinding = new Binding("IsExpanded")
{
Mode = BindingMode.TwoWay,
};
var selectedBinding = new Binding("IsSelected")
{
Mode = BindingMode.TwoWay,
};
#endregion
#region Setters
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsExpandedProperty,
Value = expandedBinding
});
itemContainerStyle.Setters.Add(new Setter
{
Property = TreeViewItem.IsSelectedProperty,
Value = selectedBinding
});
#endregion
#region EventSetters
itemContainerStyle.Setters.Add(new EventSetter
{
Event = LostFocusEvent,
Handler = new RoutedEventHandler(ItemLostLogicFocusHandler)
});
#endregion
ItemContainerStyle = itemContainerStyle;
}
private void ItemLostLogicFocusHandler(Object sender, RoutedEventArgs e)
{
e.Handled = true;
if (ItemLostLogicFocus != null)
ItemLostLogicFocus(sender, e);
}
}
I have a canvas, e.g. similar to this solution or many others using the ItemsControl.
Now I want a button which should be bound to an ICommand. This command should call a method of ViewModel class which can save the image.
The saving method is clear, but how do I do the binding following the MVVM pattern?
You could pass the Canvas to the ViewModel's Save method using a CommandParameter
<Button Content="Save"
Command="{Binding SaveCanvasCommand}"
CommandParameter="{Binding ElenementName=myCanvas}" ?>
<Canvas x:Name="myCanvas">
<!-- Stuff to save -->
</Canvas>
And somewhere in you ViewModel or Command you'd have
void SaveCanvasCommandExecute(object parameter)
{
UIElement toSave = (UIElement)parameter;
//.. You'd probably use RenderTargetBitmap here to save toSave.
}
If you don't want to reference UI elements in your ViewModel you could use an attached behaviour:
internal static class Behaviours
{
public static readonly DependencyProperty SaveCanvasProperty =
DependencyProperty.RegisterAttached("SaveCanvas", typeof(bool), typeof(Behaviours),
new UIPropertyMetadata(false, OnSaveCanvas));
public static void SetSaveCanvas(DependencyObject obj, bool value)
{
obj.SetValue(SaveCanvasProperty, value);
}
public static bool GetSaveCanvas(DependencyObject obj)
{
return (bool)obj.GetValue(SaveCanvasProperty);
}
private static void OnSaveCanvas(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
// Save code.....
}
}
}
Then in your ViewModel you have your Command that sets a property, also on your ViewModel:
public ICommand SaveCanvasCommand
{
get
{
if (_saveCanvasCommand == null)
_saveCanvasCommand = new RelayCommand(() => { IsSaveCanvas = true; });
return _saveCanvasCommand;
}
}
And the property which is bound to your View:
public bool IsSaveCanvas
{
get { return _isSaveCanvas; }
set
{
_isSaveCanvas = value;
RaisePropertyChanged("IsSaveCanvas");
}
}
Then hooking it all up in the Xaml looks like this:
Add a Trigger on the Control that binds the value of your ViewModel property to your attached behaviour:
<UserControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="True">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaveCanvas}" Value="False">
<Setter Property="wpfApplication1:Behaviours.SaveCanvas" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
And then bind your Button / MenuItem to the ViewModels Save Command:
<Canvas.ContextMenu>
<MenuItem Header="Save" Command="{Binding SaveCanvasCommand}"/>
</Canvas.ContextMenu>
I'm attempting to setup a MenuItem that will have a submenu of page numbers that can be selected. I want to bind the ItemsSource to a list of page numbers (actually to the PageCount with a converter creating the list) and then bind the IsChecked property of each MenuItem in the sub-menu to the PageIndex. My problem is with the second binding as it too requires a converter, but that converter need to know the page number that the MenuItem represents, but I cannot figure out how to pass that information to the converter. Here's what I've tried so far.
<MenuItem Header="_Goto Page"
ItemsSource="{Binding
Path=CurrentImage.PageCount,
Converter={StaticResource countToList}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable"
Value="True"/>
<Setter Property="IsChecked"
Value="{Binding
ElementName=MainWindow,
Path=CurrentImage.PageIndex,
Mode=TwoWay,
Converter={StaticResource pageNumChecked},
ConverterParameter={Binding
RelativeSource={RelativeSource Self},
Path=Content}}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
Of course the problem is that the ConverterParameter cannot be bound as it is not a DependencyProperty. So my question is how can I pass in the information I need or is there another way to do this.
Note: I already tried putting the MenuItems inside of a ListBox which worked really well as far as the bindings are concerned, but caused the sub-menu to behave in a non-standard way. That is when you opened the sub-menu the entire ListBox was treated as one MenuItem.
Edit
So here's what I've gotten to work so far. I tried binding to a hidden ListBox but when I bound the MenuItem.ItemsSource to the 'ListBox.Items' I got the list of ints instead of a list of ListBoxItems which I needed to get the IsSelected property. So I ended up using Quartermeister's suggestion of using MultiBinding to get the IsChecked property to bind to the PageIndex in OneWay mode. To handle the other direction I used an event handler on the Click event. It's worth noting that at first I had IsCheckable set to true and was working with the Checked event, but that resulted is some odd behaviors.
<MenuItem x:Name="GotoPageMenuItem" Header="_Goto Page"
ItemsSource="{Binding Path=CurrentImage.PageCount,
Converter={StaticResource countToList}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable"
Value="False"/>
<EventSetter Event="Click"
Handler="GotoPageMenuItem_Click"/>
<Setter Property="IsChecked">
<Setter.Value>
<MultiBinding Converter="{StaticResource pageNumChecked}"
Mode="OneWay">
<Binding RelativeSource="{RelativeSource FindAncestor,
AncestorType={x:Type Window}}"
Path="CurrentImage.PageIndex"
Mode="OneWay"/>
<Binding RelativeSource="{RelativeSource Self}"
Path="Header"
Mode="OneWay"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
And here's the GotoPageMenuItem_Click code
private void GotoPageMenuItem_Click(object sender, RoutedEventArgs e)
{
var item = sender as MenuItem;
if (item != null)
{
//If the item is already checked then we don't need to do anything
if (!item.IsChecked)
{
var pageNum = (int)item.Header;
CurrentImage.PageIndex = (pageNum - 1);
}
}
}
Sounds like you are trying to build dynamic menus that control the checked state of each menu item.
I extended some code I wrote to build dynamic menus in WPF with the MVVM pattern and added the checked logic.
Here is the XAML:
<Menu DockPanel.Dock="Top">
<MenuItem ItemsSource="{Binding Commands}"
Header="_Item Container Style">
<MenuItem.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="IsCheckable" Value="True"/>
<Setter Property="IsChecked" Value="{Binding Path=Checked}"/>
<Setter Property="Header" Value="{Binding Path=Text}" />
<Setter Property="Command" Value="{Binding Path=Command}" />
<Setter Property="CommandParameter" Value="{Binding Path=Parameter}" />
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
</Menu>
Here is the View Model:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
GoCommand = new DelegateCommand<object>(OnGoCommand, CanGoCommand);
LoadCommands();
}
private List<MyCommand> _commands = new List<MyCommand>();
public List<MyCommand> Commands
{
get { return _commands; }
}
private void LoadCommands()
{
MyCommand c1 = new MyCommand { Command = GoCommand, Parameter = "1", Text = "Menu1", Checked = true};
MyCommand c2 = new MyCommand { Command = GoCommand, Parameter = "2", Text = "Menu2", Checked = true };
MyCommand c3 = new MyCommand { Command = GoCommand, Parameter = "3", Text = "Menu3", Checked = false };
MyCommand c4 = new MyCommand { Command = GoCommand, Parameter = "4", Text = "Menu4", Checked = true };
MyCommand c5 = new MyCommand { Command = GoCommand, Parameter = "5", Text = "Menu5", Checked = false };
_commands.Add(c1);
_commands.Add(c2);
_commands.Add(c3);
_commands.Add(c4);
_commands.Add(c5);
}
public ICommand GoCommand { get; private set; }
private void OnGoCommand(object obj)
{
}
private bool CanGoCommand(object obj)
{
return true;
}
}
Here is the class that holds the commands:
public class MyCommand
{
public ICommand Command { get; set; }
public string Text { get; set; }
public string Parameter { get; set; }
public Boolean Checked { get; set; }
}
Can you do what you want using a MultiBinding?
<Setter Property="IsChecked">
<Setter.Value>
<MultiBinding Converter="{StaticResource pageNumChecked}">
<Binding ElementName="MainWindow" Path="CurrentImage.PageIndex" Mode="TwoWay"/>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"/>
</MultiBinding>
</Setter.Value>
</Setter>
Have your pageNumChecked converter implement IMultiValueConverter instead of IValueConverter and Convert will get an array with the result of each child binding. In this case, it would be a two-element array where the first element is your current input, PageIndex, and the second index is your current ConverterParameter, Content. If you want two-way binding, ConvertBack will need to return a two-element array, and you would return Binding.DoNothing for the second parameter so that it does not try to update Content.