ContentControl with Converter automatically UpdateSource - c#

How do I update the source of a ContentControl's Content Binding?
<ContentControl Content="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={local:TypeMappingConverter}, Mode=TwoWay}" />
I'm going to write an application, where on run time I want to annotate C#-Types and save this to a file and reload it. This is a Screenshot of my UI:
On left the User can select from the available Types and on the right the information of the selected Type are shown through a ContentControl.
This is my Model class:
public class TypeMapping
{
public Type MappedType
{
get => Type.GetType(MappedTypeName);
set => MappedTypeName = value.FullName;
}
public string MappedTypeName { get; set; }
public IEnumerable<PropertyMapping> MappedProperties { get; set; } = Array.Empty<PropertyMapping>();
public virtual string SomeText { get; set; }
}
I only want to store the mapped properties but not all available properties. So my TypeMapping is converted to a TypeMappingViewModel:
public class TypeMappingViewModel : TypeMapping, INotifyPropertyChanged
{
public IEnumerable<PropertyMapping> AvailableProperties { get; set; }
public override string SomeText
{
get => base.SomeText;
set { base.SomeText = value; NotifyPropertyChanged(); }
}
public TypeMappingViewModel(TypeMapping from)
{
MappedTypeName = from.MappedTypeName;
MappedProperties = from.MappedProperties;
AvailableProperties = MappedType.GetProperties().Select(pi => new PropertyMapping { PropertyName = pi.Name });
SomeText = from.SomeText;
}
public TypeMapping ToTypeMapping()
{
return new TypeMapping
{
MappedProperties = MappedProperties,
MappedTypeName = MappedTypeName,
SomeText = SomeText
};
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
This is all other classes:
public class MainViewModel
{
public ObservableCollection<TypeMapping> MappedTypes { get; set; }
= new ObservableCollection<TypeMapping>(new[]
{
new TypeMapping { MappedTypeName = "System.Threading.Tasks.Task" },
new TypeMapping { MappedTypeName = "System.Type" }
});
public TypeMapping SelectedType { get; set; }
}
public class PropertyMapping
{
public string PropertyName { get; set; }
public string SomeText { get; set; }
}
public partial class MainWindow : Window
{
public MainViewModel ViewModel { get; } = new MainViewModel();
public MainWindow()
{
InitializeComponent();
}
}
public class TypeMappingConverter : MarkupExtension, IValueConverter
{
#region IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TypeMapping typeMapping)
return new TypeMappingViewModel(typeMapping);
else
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is TypeMappingViewModel typeMappingViewModel)
return typeMappingViewModel.ToTypeMapping();
else
return value;
}
#endregion
#region MarkupExtension
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
#endregion
}
and the XAML:
<Window x:Class="ListViewHowTo.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:local="clr-namespace:ListViewHowTo"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<Style TargetType="FrameworkElement" x:Key="baseStyle">
<Setter Property="Margin" Value="3" />
</Style>
</Window.Resources>
<DockPanel>
<ListBox ItemsSource="{Binding ViewModel.MappedTypes, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
SelectedItem="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}"
DisplayMemberPath="MappedTypeName"
Style="{StaticResource baseStyle}"/>
<ContentControl Content="{Binding ViewModel.SelectedType, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}, Converter={local:TypeMappingConverter}, Mode=TwoWay}">
<ContentControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:TypeMappingViewModel}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding MappedTypeName}" Style="{StaticResource baseStyle}" />
<TextBox Text="{Binding SomeText}" Style="{StaticResource baseStyle}" />
<ListView ItemsSource="{Binding AvailableProperties}" Style="{StaticResource baseStyle}">
<ListView.View>
<GridView>
<GridViewColumn Header="PropertyName" DisplayMemberBinding="{Binding PropertyName}" />
<GridViewColumn Header="SomeText" DisplayMemberBinding="{Binding SomeText}" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</DockPanel>
</Window>
The value is properly converted. But how do I fire ConvertBack? Otherwise my entered Text is lost after changing the selected Type.
I need the ContentControl, because later there will be different types of mapping with different views, which I want to select via TemplateSelector.

The ItemsSource of the ListBox should be bound to a collection of view model instances instead of a collection of model instances that need to be converted. The conversion is not necessary at all.
public class MainViewModel
{
public ObservableCollection<TypeMappingViewModel> MappedTypes { get; }
= new ObservableCollection<TypeMappingViewModel>
{
new TypeMappingViewModel { MappedTypeName = "System.Threading.Tasks.Task" },
new TypeMappingViewModel { MappedTypeName = "System.Type" }
};
public TypeMappingViewModel SelectedType { get; set; }
}
With
DataContext = ViewModel;
in the MainWindow constructor, you would bind the Content like this, and entering text into the TextBlock would directly set the SomeText property of the appropriate view model item:
<ContentControl Content="{Binding SelectedType}">
...
<TextBox Text="{Binding SomeText}" />
...
</ContentControl>

Related

Binding different types of selected item in hierarchical treeview?

I have a hierarchical list of objects whose children are objects of a different type. The classes for them are described as follows:
public class Production
{
public string name { get; set; }
public string id { get; set; }
public List<Plant> plants { get; set; }
}
public class Plant
{
public string name { get; set; }
public string id { get; set; }
public List<Scheme> schemes { get; set; }
}
public class Scheme
{
public string name { get; set; }
public string id { get; set; }
}
And the main class which contains a list of productions and methods for filling the main menu:
public class DocumentProviderMenu
{
public List<Production> productions { get; set; }
public DocumentProviderMenu()
{
ExecuteUpdateMenu();
}
private void ExecuteUpdateMenu() {/*Uploding menu method}
Finally, the TreeView xaml:
<TreeView ItemsSource="{Binding Menu.productions}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type mod:Production}"
ItemsSource="{Binding plants}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type mod:Plant}"
ItemsSource="{Binding schemes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type mod:Scheme}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Where Menu is a property of ViewModel.So I declared the fields in ViewModel like:
public Production SelectedProduction
{
get => _SelectedProduction;
set
{
_SelectedProduction = value;
OnPropertyChanged(nameof(SelectedProduction));
}
}
private Production _SelectedProduction;
for 3 types - Production,Plant,Scheme.I can bind the selected item but only to one type (this question helped me Data binding to SelectedItem in a WPF Treeview). Is there a way to bind 3 of my types to the selected item?
These elements must have something in common, e.g. an interface:
XAML:
<Window x:Class="WpfApp1.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:local="clr-namespace:WpfApp1" Width="640" Height="480"
mc:Ignorable="d" d:DataContext="{d:DesignInstance local:Model}">
<Window.DataContext>
<local:Model />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock Text="{Binding SelectedProduction, StringFormat='{}Selected item: {0}' }" />
<TreeView ItemsSource="{Binding Productions}" SelectedItemChanged="TreeView_OnSelectedItemChanged" Grid.Row="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Production}"
ItemsSource="{Binding Plants}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Plant}"
ItemsSource="{Binding Schemes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Scheme}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1;
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void TreeView_OnSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
(DataContext as Model)!.SelectedProduction = e.NewValue as ITreeElement;
}
}
internal class Model : INotifyPropertyChanged
{
private ITreeElement? _selectedProduction;
public Model()
{
Productions = new List<ITreeElement>
{
new Production
{
Name = "production",
Plants = new List<Plant>
{
new()
{
Name = "plant 1",
Schemes = new List<Scheme>
{
new()
{
Name = "scheme 1"
},
new()
{
Name = "scheme 2"
}
}
},
new()
{
Name = "plant 2",
Schemes = new List<Scheme>
{
new()
{
Name = "scheme 3"
},
new()
{
Name = "scheme 4"
}
}
}
}
}
};
}
public List<ITreeElement> Productions { get; }
public ITreeElement? SelectedProduction
{
get => _selectedProduction;
set => SetField(ref _selectedProduction, value);
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public interface ITreeElement
{
string Name { get; set; }
string Id { get; set; }
}
public class Production : ITreeElement
{
public List<Plant> Plants { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}
public class Plant : ITreeElement
{
public List<Scheme> Schemes { get; set; }
public string Name { get; set; }
public string Id { get; set; }
}
public class Scheme : ITreeElement
{
public string Name { get; set; }
public string Id { get; set; }
}

My SelectedItem that is bonded to an attribute does not get the right data

So I have this class for my TabControl where i define all my tabs.
public class TabViewModel
{
public static int selectedPos { get; set; }
public static ObservableCollection<TabItem> Tabs { get; set; }
public TabViewModel(DocumentModel document)
{
Tabs = new ObservableCollection<TabItem>();
}
public class TabItem
{
public ICommand CloseCommand { get; }
public TabItem()
{
CloseCommand = new RelayCommand(Close);
}
public string Header { get; set; }
public string Content { get; set; }
public string Path { get; set; }
public void Close()
{
//DocumentModel document = new DocumentModel();
//document.FilePath = Tabs[selectedPos].Path;
//document.Text = Tabs[selectedPos].Content;
//File.WriteAllText(document.FilePath, Tabs[selectedPos].Content);
Tabs.RemoveAt(selectedPos);
}
}
This is the xaml file where i have th TabControl
<TabControl x:Name="TabControl" DataContext="{Binding TabView}"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding selectedPos,
Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
<TabControl.ItemTemplate >
<DataTemplate >
<DockPanel>
<TextBlock
Text="{Binding Header}" />
</DockPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
My problem is witt the selectedPos of Tabs.Each time selectedPos stays to 0 even if I go to another tab that is in a different position.I just need selectedPos to get the correct position.
selectedPos should be a (non-static) instance property:
public int selectedPos { get; set; }
Otherwise, i.e. if it is static, you should bind to it like this:
SelectedIndex="{Binding Path=(local:TabViewModel.selectedPos)}"
Also note that you should use the SelectedIndex property of the TabControl to bind to an int source property.
SelectedItem refers to the currently selected TabItem object.

TabControl the variable changes in all TabItems

I have a list(TabItemViewModel) which are binding to TabControl to generate TabItems and inside TabItemViewModel class I have second list(LanguageTexts) with some strings. But when I change variable value in anyone element in class LanguageTexts hViewModel.Items[0].languageTexts[0].ownedVersion = "test"; this are changing in all tabs, but I want only to change in one particular tab.
XAML:
<TabControl ItemsSource="{Binding Path=Items}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Path=Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Path=languageTexts[0].ownedVersion}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" FontSize="18"/>
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
C#
public class TabControlViewModel
{
public ObservableCollection<TabItemViewModel> Items { get; set; } = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel {Name="Tab 1", IsSelected = true },
new TabItemViewModel {Name="Tab 2" },
new TabItemViewModel {Name="Tab 3" },
new TabItemViewModel {Name="Tab 4" },
};
}
public class TabItemViewModel
{
public ObservableCollection<LanguageTexts> languageTexts { get; private set; } = new ObservableCollection<LanguageTexts>();
public string Name { get; set; }
private bool isSelected;
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
DoSomethingWhenSelected();
}
}
private void DoSomethingWhenSelected()
{
if (isSelected)
Debug.WriteLine("You selected " + Name);
}
}
public class LanguageTexts : INotifyPropertyChanged
{
private string _ownedVersion;
public string ownedVersion
{
get
{
return _ownedVersion;
}
set
{
if (value != _ownedVersion)
{
_ownedGameVersionTXT = value;
OnPropertyChanged();
}
}
}
public MainWindow()
{
InitializeComponent();
LanguageTexts languageTexts = new LanguageTexts("en_US");
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(languageTexts);
}
Since LanguageTexts is a class, each reference in your view models reference the same texts (class is a reference type). For each view to have its own copy, you would need to make a new copy for each view.
foreach (var item in hViewModel.Items)
{
item.languageTexts.Add(new LanguageTexts("en_US"));
}

How to group the data in a DataGrid on a certain condition?

How do I get in the GridView to create groups for State if he is of InProgress, and all other options have been without any group?
public class RecordVm: VmBase
{
public int Id { get; set; }
public string Description { get; set; }
public State State { get; set; }
public bool IsCompeleted { get; set; }
}
public enum State
{
Empty, Opened, InProgress, Completed
}
public class MainVm : VmBase
{
public ObservableCollection<RecordVm> RecordVms { get; } = new ObservableCollection<RecordVm>();
public ListCollectionView ListCollection {get;}
public MainVm()
{
ListCollection = new ListCollectionView(RecordVms);
ListCollection.GroupDescriptions?.Add(new PropertyGroupDescription("State"));
}
}
At the moment, I have created a group for each of the variants of State, but such an option does not suit me.
<DataGrid ItemsSource="{Binding ListCollection}"
Style="{StaticResource AzureDataGrid}"
RowStyle="{DynamicResource DataGridRowStyleStateGreen}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Margin="5,0,0,0" Text="{Binding Path=ItemCount}"/>
<TextBlock Text=" Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
enter code here
If i understood you correctly, this might group the way you want it to:
Xaml remains Untouched!
Model
public class RecordVm
{
public int Id { get; set; }
public string Description { get; set; }
public State State {
get { return this._state; }
set { this._state = value;
if (value == State.InProgress)
this.InProgress = true;return;
this.InProgress = false; }
}
private State _state;
public bool IsCompeleted { get; set; }
public bool InProgress { get; private set; }
}
Converter
public class DisplayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return "";
if ((bool) value) return "In Progress";
return "Finished";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Usage
ListCollection.GroupDescriptions?.Add(new PropertyGroupDescription("InProgress", new DisplayConverter()));
Please see if this solves your issue :
ListCollection = new ListCollectionView(RecordVms);
ListCollection.GroupDescriptions?.Add(new PropertyGroupDescription("State"));
ListCollection.Refresh();
CollectionViewGroup group = (CollectionViewGroup) ListCollection.Groups[0];
ListCollectionView viewOfGroup1 = new ListCollectionView(group.Items);
viewOfGroup1.Filter = ((i) => { return ((RecordVm)i).State == State.InProgress; });
viewOfGroup1.Refresh();

ContextMenu based on enum in bound object

My Node class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
namespace FrontEnd
{
public enum NodeType
{
SQLite,
Database,
TableCollection,
ViewCollection,
IndexCollection,
TriggerCollection,
ColumnCollection,
Table,
View,
Column,
Index,
Trigger
}
public class Node
{
public string Title { get; protected set; }
public NodeType Type { get; protected set; }
public ObservableCollection<Node> Nodes { get; set; }
public Node(string title, NodeType type)
{
this.Title = title;
this.Type = type;
this.Nodes = new ObservableCollection<Node>();
}
}
}
My XAML:
<TreeView Name="dbTree" Padding="0,5,0,0">
<TreeView.Resources>
<ContextMenu x:Key="ScaleCollectionPopup">
<MenuItem Header="New Scale..."/>
</ContextMenu>
<ContextMenu x:Key="ScaleItemPopup">
<MenuItem Header="Remove Scale"/>
</ContextMenu>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Type, RelativeSource={RelativeSource Self}}" Value="NodeType.Column">
<Setter Property="ContextMenu" Value="{StaticResource ScaleItemPopup}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<Image Source="{Binding Converter={StaticResource StringToImageConverter}}" />
<TextBlock Text="{Binding Title}" Padding="5,0,0,0" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
What I am trying to achieve and failing is to decide on the ContextMenu to use based on the Type property of the bound Node classes.
If its a Table or View I would like to display "SELECT 1000 ROWS" & "SHOW CREATE SQL", for other types I want to define other options.
What is the correct way to achieve the desired effect?
I prefer to do it in mvvm style when the context menu is generated by view model of each node. See the example below:
View part:
<Window x:Class="WpfApplication1.MainWindow"
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:wpfApplication1="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
d:DataContext="{d:DesignInstance wpfApplication1:ViewModel}">
<Grid>
<TreeView ItemsSource="{Binding Path=Nodes}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Nodes}">
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenu}">
<ContextMenu.Resources>
<Style TargetType="MenuItem">
<Setter Property="Command" Value="{Binding Command}"/>
</Style>
</ContextMenu.Resources>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding Title}"/>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And view model part:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class MenuItem
{
public MenuItem()
{
Items = new ObservableCollection<MenuItem>();
}
public string Title { get; set; }
public ICommand Command { get; set; }
public ObservableCollection<MenuItem> Items { get; private set; }
}
public class ViewModel
{
public ViewModel()
{
Nodes = new ObservableCollection<Node>
{
new Node("MSSQL", NodeType.Database,
new Node("Customers", NodeType.Table)),
new Node("Oracle", NodeType.Database)
};
}
public ObservableCollection<Node> Nodes { get; set; }
}
public enum NodeType
{
Database,
Table,
}
public class Node
{
public string Title { get; protected set; }
public NodeType Type { get; protected set; }
public ObservableCollection<Node> Nodes { get; set; }
public Node(string title, NodeType type, params Node[] nodes)
{
this.Title = title;
this.Type = type;
this.Nodes = new ObservableCollection<Node>();
if (nodes != null)
nodes.ToList().ForEach(this.Nodes.Add);
}
public IEnumerable<MenuItem> ContextMenu
{
get { return createMenu(this); }
}
private static IEnumerable<MenuItem> createMenu(Node node)
{
switch (node.Type)
{
case NodeType.Database:
return new List<MenuItem>
{
new MenuItem {Title = "Create table...", Command = new RelayCommand(o => MessageBox.Show("Table created"))}
};
case NodeType.Table:
return new List<MenuItem>
{
new MenuItem {Title = "Select..."},
new MenuItem {Title = "Edit..."}
};
default:
return null;
}
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
(you can use any implementation ICommand interface, RelayCommand is one of them)
You can generate menu items in the Node class or in an IContextMenuBuilder service that can be passed into the Node constructor.

Categories