Focus TreeViewItem (with binding and virtualization) - c#

I have this TreeView control:
<TreeView x:Name="treeView" HorizontalAlignment="Left" Margin="0" Width="300" ItemsSource="{Binding TvMview.FirstNodes}"
VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SecondNodes}">
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding ThirdNodes}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding HeaderText}" Background="Red"/>
</StackPanel>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text=">>> " Background="Green"/>
<TextBlock Text="{Binding HeaderText}" Background="blue"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Binded to:
public class ModelView
{
public TreeViewModelView TvMview { get; set; } = new TreeViewModelView();
}
public class TreeViewModelView
{
public ObservableCollection<FirstNode> FirstNodes { get; set; } = new ObservableCollection<FirstNode>();
}
public class FirstNode : TreeItem
{
public ObservableCollection<SecondNode> SecondNodes { get; set; } = new ObservableCollection<SecondNode>();
public string HeaderText { get; set; }
}
public class SecondNode : TreeItem
{
public ObservableCollection<ThirdNode> ThirdNodes { get; set; } = new ObservableCollection<ThirdNode>();
public string HeaderText { get; set; }
}
public class ThirdNode
{
public string HeaderText { get; set; }
}
public class TreeItem
{
public bool IsExpanded { get; set; }
public bool IsSelected { get; set; }
}
And everything is working as expected. But... I want to implement seach:
foreach (var firstNode in Mview.TvMview.FirstNodes)
{
if (firstNode.HeaderText.Contains(textBox.Text))
{
firstNode.IsExpanded = true;
firstNode.IsSelected = true;
TreeViewItem tvItem = (TreeViewItem)treeView.ItemContainerGenerator.ContainerFromItem(firstNode);
tvItem?.Focus();
return;
}
}
The right node is expanded but tvItem is always null. I read somewhere sometime that ContainerFromItem doesn't likes virtualization...
Any idea?
Thanks!

Finally i solved this issue implementing a Focus() method in the TreeItem base class. So, base class look like this:
public class TreeItem : INotifyPropertyChanged
{
private bool _IsExpanded;
public bool IsExpanded
{
get { return _IsExpanded; }
set { _IsExpanded = value; OnPropertyChanged("IsExpanded"); }
}
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set { _IsSelected = value; OnPropertyChanged("IsSelected"); }
}
public void Focus(TreeView tView)
{
TreeViewItem tvItem = (TreeViewItem)tView.ItemContainerGenerator.ContainerFromItem(this);
tvItem?.Focus();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
And you can call it like this:
if (firstNode.HeaderText.Contains(textBox.Text))
{
firstNode.IsExpanded = true;
firstNode.IsSelected = true;
firstMatch.Focus();
}

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

ObservableCollection not updating my GridViewColumns (MVVM)

Hi please could somebody help? I have a ListView which is bound to a ObservableCollection i.e StabCollection. The data displays perfectly.
I have a couple of textboxes and a combo box to enable the user to edit the columns. When those textboxes are edited the values change in the gridview as expected however I am trying to achieve a cancel feature to revert changes back to the original. Manually changing the SelectedItem of StabCollection or changing the StabCollection[SelectedIndex] doesn't update the UI however the collection is changing. Any help will be appreciated.
<Window.DataContext>
<local:MoveStabViewModel/>
</Window.DataContext>
<StackPanel>
<Menu IsEnabled="{Binding ListViewEnabled}">
<MenuItem Header="File"/>
<MenuItem Header="Add" Command="{Binding AddCommand}"/>
<MenuItem Header="Delete"/>
</Menu>
<StackPanel Orientation="Horizontal">
<ListView ItemsSource="{Binding StabCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding ListViewEnabled}" SelectedIndex="{Binding SelectedIndex}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="10" >
<ListView.Resources>
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
<Style TargetType="GridViewColumnHeader">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Padding" Value="5"/>
</Style>
</ListView.Resources>
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Position}" Width="Auto" Header="Position (m)"/>
<GridViewColumn DisplayMemberBinding="{Binding Type}" Width="Auto" Header="Type"/>
<GridViewColumn DisplayMemberBinding="{Binding Pot}" Width="Auto" Header="Tip (mV)"/>
<GridViewColumn DisplayMemberBinding="{Binding Current}" Width="Auto" Header="Current (mA)"/>
<GridViewColumn DisplayMemberBinding="{Binding CurrentDensity}" Width="Auto" Header="Current Density (mA/m2)"/>
<GridViewColumn DisplayMemberBinding="{Binding FieldGradient}" Width="Auto" Header="Field Gradient (uV/cm)"/>
</GridView>
</ListView.View>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding EditCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListView>
<GroupBox Header="Edit Stabs" Width="Auto" Visibility="{Binding IsEdit, Converter={StaticResource BoolToVisConverter }}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Content="Position" Grid.Column="0" Grid.Row="0"/>
<Label Content="Contact Type" Grid.Column="0" Grid.Row="1"/>
<Label Content="Potential" Grid.Column="0" Grid.Row="3"/>
<TextBox Text="{Binding SelectedItem.Position, Mode=TwoWay}" Grid.Row="0" Grid.Column="1"/>
<ComboBox x:Name="PrintOutComboBox" ItemsSource="{Binding PrintOut}" Width="220" SelectedItem="{Binding SelectedItem.Type, Mode=TwoWay}"
Grid.Row="1" Grid.Column="1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding ButtonOneCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<ComboBox SelectedIndex="{Binding ElementName=PrintOutComboBox, Path=SelectedIndex}" Width="{Binding ElementName=PrintOutComboBox ,Path=Width}"
ItemsSource="{Binding LongPrintOut}" Grid.Row="2" Grid.Column="1"/>
<TextBox Text="{Binding SelectedItem.Pot, Mode=TwoWay}" Grid.Row="3" Grid.Column="1"/>
<UniformGrid Grid.Row="4" Grid.ColumnSpan="2" Columns="2">
<Button Content="{Binding ButtonOne}" Command="{Binding ButtonOneCommand}"/>
<Button Content="Cancel" Command="{Binding CancelCommand}"/>
</UniformGrid>
</Grid>
</GroupBox>
</StackPanel>
</StackPanel>
public class MoveStabViewModel : INotifyPropertyChanged, IDialogRequestClose
{
ChartEditViewModel VM;
List<AnnotationFile> annotation;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
string[] _printOut;
public string[] PrintOut
{
get { return _printOut; }
set { _printOut = value; }
}
string[] _longPrintOut;
public string[] LongPrintOut
{
get { return _longPrintOut; }
set { _longPrintOut = value; }
}
private string[] SplitCommaDelimitedString(string value)
{
string[] splitString = value.Split(',');
return splitString;
}
ObservableCollection<StabTypes> stabTypes;
public ObservableCollection<StabTypes> StabTypes { get { return stabTypes; } }
public MoveStabViewModel()
{
}
public MoveStabViewModel(ChartEditViewModel VM)
{
this.VM = VM;
annotation = VM.annotation;
PrintOut = new string[21];
LongPrintOut = new string[21];
stabTypes = new ObservableCollection<StabTypes>();
for (int i = 0; i < 20; i++)
{
PrintOut[i] = VM.printOut[i + 1];
LongPrintOut[i] = VM.printLongName[i + 1];
stabTypes.Add(new StabTypes { Code = PrintOut[i], Description = LongPrintOut[i] });
}
EditCommand = new ActionCommand(p => LstContactsDoubleClick());
CancelCommand = new ActionCommand(p => Cancel());
ButtonOneCommand = new ActionCommand(p => ButtonOneMethod());
AddCommand = new ActionCommand(p => Add());
LoadStabs();
}
private void ButtonOneMethod()
{
if (ButtonOne == "Add Stab")
{
}
else
{
//StabCollection[SelectedIndex].Position = SelectedItem.Position;
//StabCollection[SelectedIndex].Pot = SelectedItem.Pot;
//StabCollection[SelectedIndex].Type = DisplayStab;
}
}
private void Add()
{
SelectedItem = null;
DisplayStab = "";
ListViewEnabled = false;
if (SelectedItem != null)
{
DisplayStab = SelectedItem.Type;
}
else
{
DisplayStab = "";
}
ButtonOne = "Add Stab";
WindowSize += 342;
}
public List<string[]> TempAnno { get; set; }
private ObservableCollection<AnnotationBreakDown> _stabBreakdown = new ObservableCollection<AnnotationBreakDown>();
public ObservableCollection<AnnotationBreakDown> StabCollection
{
get { return _stabBreakdown;}
set { _stabBreakdown = value; OnPropertyChanged("StabCollection"); }
}
public ICommand EditCommand { get; }
public ICommand CancelCommand { get; }
public ICommand AddCommand { get; }
public ICommand ButtonOneCommand { get; }
public event EventHandler<DialogCloseRequestedEventArgs> CloseRequested;
public event PropertyChangedEventHandler PropertyChanged;
public void LoadStabs()
{
var annotationFile = new AnnotationFile();
int j = 0;
for (int i = 0; i < annotation.Count; i++)
{
if (annotation[i]._type == "E")
{
var tempStringArray = SplitCommaDelimitedString(annotation[i]._anno);
int stabType = annotationFile.EvalStabType(Strings.Mid(annotation[i]._anno, 1, 1));
bool potCorrectFormat = float.TryParse(tempStringArray[0].Substring(1, 5), out float pot);
bool currentCorrectFormat = float.TryParse(tempStringArray[1], out float current);
bool cdCorrectFormat = float.TryParse(tempStringArray[2], out float cd);
bool fgCorrectFormat = float.TryParse(tempStringArray[3], out float fg);
bool mcCorrectFormat = float.TryParse(tempStringArray[4], out float mc); //McCoy Current
bool mcCDCorrectFormat = float.TryParse(tempStringArray[5], out float mcd);
bool mcFGCorrectFormat = float.TryParse(tempStringArray[6], out float mfg);
StabCollection.Add(new AnnotationBreakDown());
StabCollection[j].Position = Strings.Format(annotation[i]._KP, "#####0.0");
StabCollection[j].Type = VM.printOut[stabType];
if (potCorrectFormat) StabCollection[j].Pot = pot;
if (currentCorrectFormat) StabCollection[j].Current = current;
if (cdCorrectFormat) StabCollection[j].CurrentDensity = cd;
if (fgCorrectFormat) StabCollection[j].FieldGradient = fg;
if (mcCorrectFormat) StabCollection[j].MC = mc;
if (mcCDCorrectFormat) StabCollection[j].MCD = mcd;
if (mcFGCorrectFormat) StabCollection[j].MFG = mfg;
j += 1;
}
}
}
//Are we editing the stabs?
private bool _listviewEnabled = true;
public bool ListViewEnabled
{
get { return _listviewEnabled; }
set { _listviewEnabled = value; OnPropertyChanged("ListViewEnabled"); }
}
//The selected item from listview
private AnnotationBreakDown _selectedItem = new AnnotationBreakDown();
public AnnotationBreakDown SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; OnPropertyChanged("SelectedItem"); }
}
private string _displayStab;
public string DisplayStab
{
get { return _displayStab; }
set { _displayStab = value; OnPropertyChanged("DisplayStab"); }
}
//The listview Selected Index
int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; OnPropertyChanged("SelectedIndex"); }
}
//Property to store Add/Accept Button
private string _buttonOne = "Accept";
public string ButtonOne
{
get { return _buttonOne; }
set { _buttonOne = value; OnPropertyChanged("ButtonOne"); }
}
string tempPos;
string tempType;
float tempPot;
//Event called when mouse is double clicked on listview
public void LstContactsDoubleClick()
{
if (SelectedItem == null) return;
tempPos = StabCollection[SelectedIndex].Position;
tempType = StabCollection[SelectedIndex].Type;
tempPot = StabCollection[SelectedIndex].Pot;
ListViewEnabled = false;
DisplayStab = SelectedItem.Type;
WindowSize += 342;
}
//Dynamic Window Size CLR property
private int _windowSize = 610;
public int WindowSize
{
get { return _windowSize; }
set { _windowSize = value; OnPropertyChanged("WindowSize"); }
}
public void Cancel()
{
ListViewEnabled = true;
SelectedItem.Type = "GP";
WindowSize -= 342;
}
}
public class StabTypes
{
public string Code { get; set; }
public string Description { get; set; }
public string CodeToolTip
{
get { return Description; }
}
}
public class AnnotationBreakDown
{
public string Position { get; set; }
public string Type { get; set;}
public float Pot { get; set; }
public float Current { get; set; }
public float CurrentDensity { get; set; }
public float FieldGradient { get; set; }
public float MC { get; set; } //McCoy Current
public float MCD { get; set;} //McCoy CD
public float MFG { get; set; } //McCoy FG
}
}
Because ObservableCollection doesn't observe his items. It will raise an event for an insert, delete an item, or reset the collection, not a modification on his item.
So, you must implement ObservableCollection which observe equally his items. This code used in my project found on SO but I can't figure out the post original.
public class ItemsChangeObservableCollection<T> :
System.Collections.ObjectModel.ObservableCollection<T> where T : INotifyPropertyChanged
{
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
RegisterPropertyChanged(e.NewItems);
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
UnRegisterPropertyChanged(e.OldItems);
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
UnRegisterPropertyChanged(e.OldItems);
RegisterPropertyChanged(e.NewItems);
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
UnRegisterPropertyChanged(this);
base.ClearItems();
}
private void RegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void UnRegisterPropertyChanged(IList items)
{
foreach (INotifyPropertyChanged item in items)
{
if (item != null)
{
item.PropertyChanged -= new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
private void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//launch an event Reset with name of property changed
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
private ItemsChangeObservableCollection<AnnotationBreakDown> _stabBreakdown = new ItemsChangeObservableCollection<AnnotationBreakDown>();
public ItemsChangeObservableCollection<AnnotationBreakDown> StabCollection
{
get { return _stabBreakdown;}
set { _stabBreakdown = value; }
}
Last but not least, your model must implement INotifyPropertyChanged
public class AnnotationBreakDown : INotifyPropertyChanged
{
public string Position { get; set; } // To raise PropertyChanged
public string Type { get; set;} // To raise PropertyChanged
public float Pot { get; set; } // To raise PropertyChanged
public float Current { get; set; } // To raise PropertyChanged
public float CurrentDensity { get; set; } // To raise PropertyChanged
public float FieldGradient { get; set; } // To raise PropertyChanged
public float MC { get; set; } //McCoy Current
public float MCD { get; set;} //McCoy CD
public float MFG { get; set; } //McCoy FG
}

UI won't update, when adding an item to an ObservableCollection with a Command (WPF, C#)

I have a TreeView, where I want to add an item with a right click. Therefore I use a command, to insert a new item.
The problem is now that adding/removing items won't be reflected in the UI and I don't know why.
The ObersavableCollections are hold in a property in my MainWindow
public static ObservableCollection<PlantObject> PlantObjects { get; set; }
The code works the way I want thus if I check the property and the item itself, it will be added but the TreeView will not reflect the changes. This can be shown by setting the ItemsSource of the TreeView null and then again to PlantObjects, which then displays the added item.
I'm pretty sure it the problem is that I add an item to the property "parents". But I don't know how to solve it.
You can see my Xaml and my Class below. NotifyPropertyChanged is implemented.
Thanks for any advice.
XAML
<TreeView Name="ContextTreeView" Margin="0,9.8,0.8,5" Grid.Row="1" Grid.Column="0" ItemsSource="{Binding Source={x:Static local:MainWindow.PlantObjects} }">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:PlantObject}" ItemsSource="{Binding PlantObjects}" >
<StackPanel Orientation="Horizontal">
<StackPanel.ContextMenu>
<ContextMenu >
<MenuItem Header="Delete" Name="MenuItem_DeleteContextType" Command="{Binding DeleteContextTypeCommand}"/>
<MenuItem Header="Insert new object above" Name="ContextMenuItem_InsertAbovePlantObject" Command="{Binding InsertAboveCommand}"/>
</ContextMenu>
</StackPanel.ContextMenu>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
My Class
public class PlantObject : INotifyPropertyChanged
{
public PlantObject()
{
CreateInsertAboveCommand();
}
private bool _IsSelected;
private PlantObject parent;
private string name;
public byte[] ParentID { get; set; }
public byte[] ChildID { get; set; }
public byte[] ObjectID { get; set; }
private ObservableCollection<PlantObject> plantObjects = new ObservableCollection<PlantObject>();
public PlantObject Parent
{
get
{
return parent;
}
set
{
if (value != parent)
{
parent = value;
NotifyPropertyChanged();
}
}
}
public string Name
{
get
{
return name;
}
set
{
if (value != name)
{
name = value;
NotifyPropertyChanged();
}
}
}
public ObservableCollection<PlantObject> PlantObjects
{
get { return plantObjects; }
set
{
//ignore if values are equal
if (value == plantObjects) return;
plantObjects = value;
NotifyPropertyChanged();
}
}
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (_IsSelected == value) return;
_IsSelected = value;
NotifyPropertyChanged();
}
}
public int? GetPosition(PlantObject plantObject)
{
int count = 0;
foreach (var childObject in this.plantObjects)
{
if (plantObject == childObject)
{
return count;
}
count++;
}
return null;
}
protected void Insert(int position, PlantObject plantObject)
{
Parent.PlantObjects.Insert(position, plantObject);
}
protected void Remove(PlantObject plantObject)
{
Parent.PlantObjects.RemoveAt(Parent.PlantObjects.IndexOf(plantObject));
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region commands
#region InsertAbove Command
public ICommand InsertAboveCommand
{
get;
internal set;
}
private bool CanExecuteInsertAboveCommand()
{
return true;
}
private void CreateInsertAboveCommand()
{
InsertAboveCommand = new RelayCommand(InsertAbove);
}
public void InsertAbove(object obj)
{
//this.IsSelected = true;
if (this.parent == null)
{ //highest level
MainWindow.PlantObjects.Insert(MainWindow.PlantObjects.IndexOf(this), new PlantObject {IsSelected = true, Parent = this.Parent });
return;
}
int position = this.parent.GetPosition(this) ?? default(int);
PlantObject newPlantObejct = new PlantObject { Parent = this.Parent, IsSelected = true };
this.Insert(position, newPlantObejct);
}
#endregion
#endregion
}

How to bind button to Listbox item

ViewModel
public class MainWindowViewModel:BindableBase
{
public IRelayCommand MyCommand { get; protected set; }
private void CreateCommand()
{
this.MyCommand = new RelayCommand(MyCommandExecuted, CanExecuteMyCommand);
}
private void MyCommandExecuted(object obj)
{
MessageBox.Show("Command Executed");
}
private bool CanExecuteMyCommand(object obj)
{
return true; // The value is based on Selected Item
}
}
XAML
<ListBox
x:Name="myListBox"
ItemsSource="{Binding Path=MyClass}"
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Path=HeaderName}" IsExpanded="True">
<StackPanel>
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Path=RowVal}" SelectedItem="{Binding CurrentItem}"/>
</StackPanel>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
<Button Content="Select"
Command="{Binding Path=MyCommand }"
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem}"/>
DataClass
public class DataClass
{
public string HeaderName { get; set; }
public object RowVal { get; set; }
public ObservableCollection<DataGridColumn> ColumnCollection { get; set;}
private object currentItem;
public object CurrentItem
{
get
{
return currentItem;
}
set
{
currentItem = value;
}
}
}
How can I bind my button to Listbox item which is CurrentItem in DataClass ?
I created a complete example to show how I would do it. You would have to bind the parent element to SelectedItem as well, to keep track of when that item changes. Since the SelectedItem is public in your child class as well you can access that when your command triggers in your main view model.
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<ListView ItemsSource="{Binding Parents}" SelectedItem="{Binding SelectedParent}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:Parent}">
<DataGrid ItemsSource="{Binding Children}" SelectedItem="{Binding SelectedChild}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Width="70" Content="Click me" Height="25" Command="{Binding MyCommand}" />
i.e. in DoWork, you can get the child from the parent via its public property.
public sealed class WindowViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<Parent> parents;
private readonly ICommand myCommand;
private Parent selectedParent;
public WindowViewModel()
{
parents = new ObservableCollection<Parent>
{
new Parent{ Name = "P1"},
new Parent{ Name = "P2"}
};
myCommand = new DelegateCommand(DoWork);
}
private void DoWork()
{
var selectedChild = SelectedParent == null ? null : SelectedParent.SelectedChild;
}
public Parent SelectedParent
{
get { return selectedParent; }
set
{
if (selectedParent == value)
return;
selectedParent = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Parent> Parents
{
get { return parents; }
}
public ICommand MyCommand
{
get { return myCommand; }
}
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
With the basic setup of Data models
public class Parent : INotifyPropertyChanged
{
private ObservableCollection<Child> children;
private Child m_SelectedChild;
public Parent()
{
children = new ObservableCollection<Child>
{
new Child {Name = "C1"},
new Child {Name = "C2"}
};
}
public string Name { get; set; }
public ObservableCollection<Child> Children
{
get { return children; }
}
public Child SelectedChild
{
get { return m_SelectedChild; }
set
{
if (m_SelectedChild == value)
return;
m_SelectedChild = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Child
{
public string Name { get; set; }
}
Another solution, if you are more interested of the Child item in your WindowViewModel is to change the relative source of where the binding should occur, in your DataGrid. i.e., the binding would look like this instead:
<DataGrid ItemsSource="{Binding Children}"
SelectedItem="{Binding DataContext.SelectedChild, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}" />
and then move the Property from Parent to WindowViewModel. With that you would be able to trigger changes to your button command when the child element changes for any of the Parent elements.
I think you want to pass the CurrentItem to the MyCommand as CommandParameter right?
Then you only have to:
CommandParameter="{Binding CurrentItem, UpdateSourceTrigger=PropertyChanged}"
Try this :
CommandParameter="{Binding ElementName=myListBox,Path=SelectedItem.CurrentItem}"

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