I'm trying to display a treeview in my application but somehow I can't get the binding to work and application displays a blank treeview. Here's the source code.
MainWindow.xaml
<Window x:Class="TreeViewTest.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:TreeViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<ListBox x:Name="listbox" MinWidth="200" MinHeight="300">
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Regions}"
MinWidth="200" MinHeight="300">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Region}" ItemsSource="{Binding Customers}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Customer}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:CustomerDataHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</ListBox>
<Button Content="?" Click="Button_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public ObservableCollection<Region> Regions { get; }
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
if (CustomerDataHelper.GetIsChecked(Customer) == true)
crew += Customer.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Selected customers: " + crew;
}
}
MainWindowViewModel.cs
class MainWindowViewModel
{
public MainWindowViewModel()
{
Regions = new ObservableCollection<Region>();
Regions.Add(new Region() { Name = "North", Customers = new List<Customer>() { new Customer() { Name = "N1" }, new Customer() { Name = "N2" } } });
Regions.Add(new Region() { Name = "East", Customers = new List<Customer>() { new Customer() { Name = "E1" }, new Customer() { Name = "E2" } } });
Regions.Add(new Region() { Name = "South", Customers = new List<Customer>() { new Customer() { Name = "S1" } } });
foreach (Region Region in this.Regions)
foreach (Customer Customer in Region.Customers)
Customer.SetValue(CustomerDataHelper.ParentProperty, Region);
}
public ObservableCollection<Region> Regions { get; }
}
Customers.cs
public class Region : DependencyObject
{
public string Name { get; set; }
public List<Customer> Customers { get; set; }
}
public class Customer : DependencyObject
{
public string Name { get; set; }
}
CustomerDataHelper.cs
public class CustomerDataHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CustomerDataHelper), new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element.SetValue(CustomerDataHelper.IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element.GetValue(CustomerDataHelper.IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(CustomerDataHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element.SetValue(CustomerDataHelper.ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return (object)element.GetValue(CustomerDataHelper.ParentProperty);
}
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Region && ((bool?)e.NewValue).HasValue)
foreach (Customer p in (d as Region).Customers)
CustomerDataHelper.SetIsChecked(p, (bool?)e.NewValue);
if (d is Customer)
{
int checkedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == true).Count();
int uncheckedCount = ((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as Region).Customers.Where(x => CustomerDataHelper.GetIsChecked(x) == false).Count();
if (uncheckedCount > 0 && checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, null);
return;
}
if (checkedCount > 0)
{
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, true);
return;
}
CustomerDataHelper.SetIsChecked((d as Customer).GetValue(CustomerDataHelper.ParentProperty) as DependencyObject, false);
}
}
}
If I add the view model ctor code in the code behind ctor and run, it works and I can see the tree view items displayed. Can't seem to figure out what I'm missing.
Related
I am doing a user control, to be able to display logs. I would like the user to be able to select lines to copy and paste. I plan to use a RichTextBox, because depending on the type of log line (Warn, Info, Error), I change the color.
Here is the code:
<UserControl x:Class="WpfAppFrameLogging.UserControls.LoggerControl"
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:WpfAppFrameLogging.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<RichTextBox x:Name="RichText"
IsReadOnly="True"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument x:Name="FlowDoc">
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
</FlowDocument.Resources>
</FlowDocument>
</RichTextBox>
</Grid>
</UserControl>
There, I will want with a dependency properties of type ObservableCollection , and when there is an addition of a log line in my controller, that it adds in the RichTextBox a new paragraph.
Something that would look like this.
Codebehind UserControl
var paragraphLog = new Paragraph(new Run(textLog));
paragraphLog.Margin = new Thickness(0, 0, 0, 0);
switch (status)
{
case StatusLog.Error:
paragraphLog.Foreground = Brushes.Red;
break;
case StatusLog.Success:
paragraphLog.Foreground = Brushes.Green;
break;
case StatusLog.Warn:
paragraphLog.Foreground = Brushes.Orange;
break;
case StatusLog.Info: // Useless--> I know
default:
paragraphLog.Foreground = Brushes.Black;
break;
}
FlowDoc.Blocks.Add(paragraphLog);
RichText.ScrollToEnd();
Class LogInfo
public class LogInfo
{
public string TextLog { get; set; }
public StatusLog StatusLog { get; set; }
}
public enum StatusLog
{
Info, // --> Black
Warn, // --> Orange
Error, // --> Red
Success // --> Green
}
And in the views I'll be using the UserControl:
<myControls:LoggerControl AllLogsInfo="{Binding MainLogInfos, Mode=OneWay}" />
With MainLogInfos
public ObservableCollection<LogInfo> MainLogInfos
{
get { return _mainLogInfos; }
set
{
if (value != _mainLogInfos)
{
_mainLogInfos = value;
NotifyPropertyChanged();
}
}
}
private ObservableCollection<LogInfo> _mainLogInfos;
// Method :
public void LogError(string message)
{
MainLogInfos.Add(new LogInfo() { TextLog = message, StatusLog = StatusLog.Error });
}
I am limited to the 4.7.2 framework.
Thank you for your help, ideas, suggestions ...
This example are using a Listbox with a TextBox. It is set to look like a TextBlock. The Style are used to set the color depending on the StatusLog. I have included 4 log messages with different status. You can select multiple lines and I have included a context menu for copying the selected text.
MainWindow.xaml
<Window x:Class="SO65185215.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:SO65185215"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox ItemsSource="{Binding MainLogInfos}">
<ListBox.Resources>
<Style x:Key="TextBoxStyle" TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusLog}" Value="Warn">
<Setter Property="Foreground" Value="Orange"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusLog}" Value="Error">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding StatusLog}" Value="Success">
<Setter Property="Foreground" Value="Green"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox x:Name="tb" Text="{Binding TextLog}"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"
Style="{StaticResource TextBoxStyle}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Command="ApplicationCommands.Copy"/>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace SO65185215
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += MainWindow_Loaded;
}
public ObservableCollection<LogInfo> MainLogInfos { get; } = new ObservableCollection<LogInfo>();
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MainLogInfos.Add(new LogInfo() { TextLog = "log message 1", StatusLog = StatusLog.Info });
MainLogInfos.Add(new LogInfo() { TextLog = "log message\nNext line message 2", StatusLog = StatusLog.Error });
MainLogInfos.Add(new LogInfo() { TextLog = "log message3\nWarning", StatusLog = StatusLog.Warn });
MainLogInfos.Add(new LogInfo() { TextLog = "log message success", StatusLog = StatusLog.Success });
}
}
public class LogInfo
{
public string TextLog { get; set; }
public StatusLog StatusLog { get; set; }
}
public enum StatusLog
{
Info, // --> Black
Warn, // --> Orange
Error, // --> Red
Success // --> Green
}
}
Edit:
If your purpose was to select several messages, then you can set the ListBox to Multipel/Extended selectionMode:
<ListBox x:Name="listBox" ItemsSource="{Binding MainLogInfos}" SelectionMode="Multiple">
Move the context menu to the Listbox
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Copy selected" Click="CopySelected_Clicked"/>
</ContextMenu>
</ListBox.ContextMenu>
Concatenate the selected texts to the clipboard in the Click event handler.
Observe that the selectedItems collection contains messages in the order the messages are selected.
private void CopySelected_Clicked(object sender, RoutedEventArgs e)
{
StringBuilder sb = new StringBuilder();
foreach(LogInfo item in listBox.SelectedItems)
{
sb.AppendLine(item.TextLog);
}
if (sb.Length > 0)
Clipboard.SetText(sb.ToString());
}
I found a solution
My LoggerControl.xaml
<UserControl x:Class="SDK.FrontOffice.CustomControl.LoggerControl"
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:SDK.FrontOffice.CustomControl"
xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:converter="clr-namespace:SDK.FrontOffice.Converters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<!--<Grid.Resources>
<converter:TypeInfoToColorConverter x:Key="converterTypeInfo" />
</Grid.Resources>-->
<!--<GroupBox Header="Logs">-->
<RichTextBox x:Name="RichText"
IsReadOnly="True"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<FlowDocument x:Name="FlowDoc">
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
</FlowDocument.Resources>
</FlowDocument>
</RichTextBox>
<!--</GroupBox>-->
</Grid>
</UserControl>
And code behind
/// <summary>
/// Logique d'interaction pour LoggerControl.xaml
/// </summary>
public partial class LoggerControl : UserControl
{
public LoggerControl()
{
InitializeComponent();
}
#region DependencyProperty
public ObservableCollection<LogInfo> AllLogs
{
get { return (ObservableCollection<LogInfo>)GetValue(AllLogsProperty); }
set { SetValue(AllLogsProperty, value); }
}
// Using a DependencyProperty as the backing store for AllLogs. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AllLogsProperty =
DependencyProperty.Register("AllLogs",
typeof(ObservableCollection<LogInfo>),
typeof(LoggerControl),
new PropertyMetadata(new ObservableCollection<LogInfo>(),
OnAllLogsChanged));
private static void OnAllLogsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
LoggerControl loggerControl = sender as LoggerControl;
var old = e.OldValue as ObservableCollection<LogInfo>;
if (old != null)
{
old.CollectionChanged -= loggerControl.OnWorkCollectionChanged;
}
var nouvelleValeur = e.NewValue as ObservableCollection<LogInfo>;
if (nouvelleValeur != null)
{
nouvelleValeur.CollectionChanged += loggerControl.OnWorkCollectionChanged;
}
// Reset de la liste.
if(old.Count > 0 && nouvelleValeur.Count == 0)
loggerControl.FlowDoc.Blocks.Clear();
}
}
/// <summary>
/// Méthode quand la collection est modifiée.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Reset)
{
// Clear and update entire collection
this.FlowDoc.Blocks.Clear();
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
var nouvelleCollection = sender as ObservableCollection<LogInfo>;
var derniereLigne = nouvelleCollection.Last();
Paragraph paragraphLog = new Paragraph(new Run(derniereLigne.InformationMessage));
paragraphLog.Margin = new Thickness(0, 0, 0, 0);
witch (derniereLigne.StatusLog)
case StatusLog.Error:
paragraphLog.Foreground = Brushes.Red;
break;
case StatusLog.Warn:
paragraphLog.Foreground = Brushes.Orange;
break;
case StatusLog.InfoGreen:
paragraphLog.Foreground = Brushes.Green;
break;
case StatusLog.Information:
default:
paragraphLog.Foreground = Brushes.Black;
break;
this.FlowDoc.Blocks.Add(paragraphLog);
this.RichText.ScrollToEnd();
}
}
}
I have a window which contains a TabControl and I would like to have TabItems generated based on user actions. Inside the TabItems I would like to display a UserControl which uses a ViewModel.
I can get everything to display properly, however when the UserControl's ViewModel is updated, the changes are not reflected in the TabItem.
Consider the following simplified example:
MainWindowViewModel.cs
public class MainWindowViewModel : ViewModel
{
public MainWindowViewModel()
{
MyControls = new ObservableCollection<MyControlViewModel>();
}
private ObservableCollection<MyControlViewModel> _myControls;
public ObservableCollection<MyControlViewModel> MyControls
{
get { return _myControls; }
set
{
_myControls = value;
RaisePropertyChangedEvent(nameof(MyControls));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private static MainWindowViewModel _vm;
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
_vm = DataContext as MainWindowViewModel;
var myItem1 = new MyItem("Item1");
var myItem2 = new MyItem("Item2");
var myControlVm = new MyControlViewModel(new ObservableCollection<MyItem> { myItem1, myItem2 });
_vm.MyControls.Add(myControlVm);
}
}
MainWindow.xaml
<Window x:Class="TabControlTest.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:TabControlTest"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding MyControls}">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type local:MyControlViewModel}">
<local:MyControl />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MyControlViewModel.cs
public class MyControlViewModel : ViewModel
{
public MyControlViewModel(ObservableCollection<MyItem> items)
{
MyItems = items;
}
private ObservableCollection<MyItem> _myItems;
public ObservableCollection<MyItem> MyItems
{
get { return _myItems; }
set
{
_myItems = value;
RaisePropertyChangedEvent(nameof(MyItems));
}
}
}
MyControl.xaml.cs
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
private void ListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var vm = DataContext as MyControlViewModel;
foreach (var item in vm.MyItems)
{
item.ShouldBold = !item.ShouldBold;
}
}
}
MyControl.xaml
<UserControl x:Class="TabControlTest.MyControl"
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:TabControlTest"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ListBox ItemsSource="{Binding MyItems}" MouseDoubleClick="ListBox_MouseDoubleClick">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Label}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<DataTrigger Binding="{Binding ShouldBold}" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
</Grid>
</UserControl>
MyItem.cs
public class MyItem : ViewModel
{
public MyItem(string label)
{
Label = label;
}
private string _label;
public string Label
{
get { return _label; }
set
{
_label = value;
RaisePropertyChangedEvent(nameof(Label));
}
}
private bool _shouldBold;
public bool ShouldBold
{
get { return _shouldBold; }
set
{
_shouldBold = value;
RaisePropertyChangedEvent(nameof(ShouldBold));
}
}
}
When I double-click on the MyControl in the MainWindow I would expect the items to be displayed in bold via the ListBox_MouseDoubleClick method, however the style is not applying. When I step through the ListBox_MouseDoubleClick method, I can see that the ShouldBold is being updated correctly, but again the style is not applying.
Is there another way I should structure this or something else I need to do to have the style apply? Any help is appreciated!
Edit
Here's my ViewModel.cs
public class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Your ViewModel class should implement INotifyPropertyChanged:
public class ViewModel : INotifyPropertyChanged
...
I am trying to add to the TreeView the ability to catch the IsExpanded event. So that when a certain Item will expand it will raise a property or command on the view model.
Here is my code:
<TreeView Name="ScenariosTreeView" ItemsSource="{Binding Path=Cat, Mode=TwoWay}" Focusable="True" >
<TreeView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedValue ,ElementName=ScenariosTreeView}" />
</TreeView.InputBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExtended, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type sotc:ScenarioCategory}" ItemsSource="{Binding Path=ScenarioList}" >
<TextBlock Text="{Binding Path=Name}" Foreground="Black" IsEnabled="True" Focusable="True"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sotc:Scenario}" >
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
I also have a property in the viewmodel for IsExpanded (and for IsSelected) and none of that raises when I am expand the TreeviewItem.
Any Ideas?
Thanks ahead,
Tried to reproduce the issue and implemented attached behavior for calling command when node is expanded and everything works fine. Complete code for the solution:
XAML
<Window x:Class="TreeViewTest.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:sotc="clr-namespace:TreeViewTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<sotc:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TreeView Name="ScenariosTreeView" ItemsSource="{Binding Path=Cat, Mode=TwoWay}" Focusable="True" >
<TreeView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding DeleteCommand}" CommandParameter="{Binding SelectedValue ,ElementName=ScenariosTreeView}" />
</TreeView.InputBindings>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExtended, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="sotc:Behaviours.ExpandingBehaviour" Value="{Binding DataContext.ExpandingCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type sotc:ScenarioCategory}" ItemsSource="{Binding Path=ScenarioList}" >
<TextBlock Text="{Binding Path=Name}" Foreground="Black" IsEnabled="True" Focusable="True"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type sotc:Scenario}" >
<TextBlock Text="{Binding Path=Name, Mode=TwoWay}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>
C#
public class ViewModelBase : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MainWindowViewModel : ViewModelBase
{
public ICommand ExpandingCommand { get; set; }
private void ExecuteExpandingCommand(object obj)
{
Console.WriteLine(#"Expanded");
}
private bool CanExecuteExpandingCommand(object obj)
{
return true;
}
public MainWindowViewModel()
{
ExpandingCommand = new RelayCommand(ExecuteExpandingCommand, CanExecuteExpandingCommand);
Cat = new ObservableCollection<ScenarioCategory>(new ScenarioCategory[]
{
new ScenarioCategory { Name = "C1" }, new ScenarioCategory { Name = "C2" }, new ScenarioCategory { Name = "C3" }
});
}
public ObservableCollection<ScenarioCategory> Cat { get; set; }
}
public class ScenarioCategory : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
bool _isExtended;
public bool IsExtended
{
get { return _isExtended; }
set
{
_isExtended = value;
Console.WriteLine(#"IsExtended set");
OnPropertyChanged();
}
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
Console.WriteLine(#"IsSelected set");
OnPropertyChanged();
}
}
public ObservableCollection<Scenario> ScenarioList { get; set; }
public ScenarioCategory()
{
ScenarioList = new ObservableCollection<Scenario>(new Scenario[] { new Scenario { Name = "1" }, new Scenario { Name = "2" }, new Scenario { Name = "3" } });
}
}
public class Scenario : ViewModelBase
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
bool _isExtended;
public bool IsExtended
{
get { return _isExtended; }
set
{
_isExtended = value;
OnPropertyChanged();
}
}
bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged();
}
}
}
public static class Behaviours
{
public static readonly DependencyProperty ExpandingBehaviourProperty =
DependencyProperty.RegisterAttached("ExpandingBehaviour", typeof(ICommand), typeof(Behaviours),
new PropertyMetadata(OnExpandingBehaviourChanged));
public static void SetExpandingBehaviour(DependencyObject o, ICommand value)
{
o.SetValue(ExpandingBehaviourProperty, value);
}
public static ICommand GetExpandingBehaviour(DependencyObject o)
{
return (ICommand)o.GetValue(ExpandingBehaviourProperty);
}
private static void OnExpandingBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TreeViewItem tvi = d as TreeViewItem;
if (tvi != null)
{
ICommand ic = e.NewValue as ICommand;
if (ic != null)
{
tvi.Expanded += (s, a) =>
{
if (ic.CanExecute(a))
{
ic.Execute(a);
}
a.Handled = true;
};
}
}
}
}
public class RelayCommand : ICommand
{
private Action<object> execute;
private Func<object, bool> canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
Found the answer. It fired the event if it is placed in the object represented as a treeviewItem and not in the view model.
I am using infragistics tabcontrol controls. My itemcontainer value is based on an object property. To be clear what I mean, look at the following code snippet.
WPF
<Window x:Class="DynamicTabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:igWindows="http://infragistics.com/Windows"
xmlns:igDp="http://infragistics.com/DataPresenter"
xmlns:igThemes="http://infragistics.com/Themes"
Title="MainWindow" Height="450" Width="700">
<Grid>
<igWindows:XamTabControl Name="xamTabCtrl" ItemsSource="{Binding Collection}" Theme="Metro">
<igWindows:XamTabControl.ItemContainerStyle>
<Style TargetType="{x:Type igWindows:TabItemEx}" BasedOn="{x:Static igThemes:DataPresenterMetro.LabelPresenter}">
<Setter Property="Header" Value="{Binding Path=Header}"/>
</Style>
</igWindows:XamTabControl.ItemContainerStyle>
<igWindows:XamTabControl.ContentTemplate>
<DataTemplate>
<igDp:XamDataGrid Theme="Metro"
DataSource="{Binding Logins}" IsGroupByAreaExpanded="False" GroupByAreaLocation="None" GroupByAreaMode="DefaultFieldLayoutOnly"/>
</DataTemplate>
</igWindows:XamTabControl.ContentTemplate>
</igWindows:XamTabControl>
</Grid>
</Window>
and the partial class
public partial class MainWindow : Window
{
private ObservableCollection<Model> collection;
public ObservableCollection<Model> Collection
{
get { return collection; }
}
public MainWindow()
{
InitializeComponent();
collection = new ObservableCollection<Model>()
{
new Model() { Header = "Tab1"},
new Model() { Header = "Tab2"},
new Model() { Header = "Tab3"},
new Model() { Header = "Tab4"},
};
DataContext = this;
//xamTabCtrl.ItemsSource = collection;
}
}
and the model
public class Model
{
private ObservableCollection<Login> _logins = new ObservableCollection<Login>()
{
new Login() { User = "User", Password = "Password"},
new Login() { User = "User", Password = "Password"},
new Login() { User = "User", Password = "Password"},
};
public string Header { get; set; }
public ObservableCollection<Login> Logins
{
get { return _logins; }
}
}
when I compile the application I've got the error
{"Can only base on a Style with target type that is base type 'TabItemEx'."}
Who can I inherit the style from infragistics theme?
I found it. I found the class for inheritance.
<Style TargetType="{x:Type igWindows:TabItemEx}" BasedOn="{x:Static igThemes:PrimitivesMetro.TabItemEx}">
<Setter Property="Header" Value="{Binding Path=Header}"/>
</Style>
In my View I have a Menu Control which is binded to my ViewModel's Property becauase I want to populate it dynamically.I created a seperated class for my Menu .
Here is my My menu class:
public class MenuItemViewModel : ViewModelBase
{
internal MenuItemViewModel()
{
}
private string _menuText;
public string MenuText
{
get { return _menuText; }
set
{
if (_menuText == value)
return;
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
private ObservableCollection<MenuItemViewModel> _children;
public ObservableCollection<MenuItemViewModel> Children
{
get { return _children; }
set
{
_children = value;
RaisePropertyChanged("Children");
}
}
}
and In my MainViewModel I created an Collection property of my MenuItemViewModel
here is my MainViewModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
LoadMainMenu();
}
#region Menu
private ObservableCollection<MenuItemViewModel> _topMenuItems;
public ObservableCollection<MenuItemViewModel> TopMenuItems
{
get { return _topMenuItems; }
set
{
if (_topMenuItems == value)
return;
_topMenuItems = value;
RaisePropertyChanged("TopMenuItems");
}
}
public void LoadMainMenu()
{
IList<MenuItemViewModel> fileMenuItems = PopulateFileMenuEntries();
_topMenuItems.Add(new MenuItemViewModel() { MenuText = "_File", Children = new ObservableCollection<MenuItemViewModel>(fileMenuItems) });
}
private IList<MenuItemViewModel> PopulateFileMenuEntries()
{
List<MenuItemViewModel> fileMenuItems = new List<MenuItemViewModel>();
fileMenuItems.Add(new MenuItemViewModel() { MenuText = "Open _Recent" });
return fileMenuItems;
}
}
here is my XAML:
<Window.Resources>
<WpfApplication3_ViewModel:MainViewModel x:Key="MainViewModelDataSource"
d:IsDataSource="True" />
</Window.Resources>
<Grid DataContext="{StaticResource MainViewModelDataSource}">
<Menu
ItemsSource="{Binding TopMenuItems}"
Margin="12,0,50,237">
<Menu.Resources>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding MenuText}" />
<Setter Property="ItemsSource"
Value="{Binding Children}" />
<Style.Triggers>
<DataTrigger Binding="{Binding }"
Value="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator Style="{StaticResource {x:Static MenuItem.SeparatorStyleKey}}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Menu.Resources>
</Menu>
</Grid>
When the application run it throw an exception "Exception has been thrown by the target of an invocation."
What is wrong with my code
I have had similar problem and I tracked it down to an uninitialised variable. Your _topMenuItems in your Constructor should be
new ObservableCollection<MenuItemViewModel>()
or
ObservableCollection<MenuItemViewModel> _topMenuItems = new ObservableCollection<MenuItemViewModel>();