WPF Menu Binding losing style only in the first level - c#

Please let me know if I am doing something wrong in my code. I am trying to bind a WPF menu to a "MenuViewModel". The binding works as I expect in a non styled Window.
I am using MahApps.Metro for styling purposes only and this is how it looks after binding.
Here's the link to the source code http://sdrv.ms/W5uJpY
ViewModel:
public class Menu : INotifyPropertyChanged
{
public Menu()
{
IsEnabled = true;
Children = new List<Menu>();
}
#region [ Menu Properties ]
private bool _isEnabled;
private string _menuText;
private ICommand _command;
private IList<Menu> _children;
public string MenuText
{
get { return _menuText; }
set
{
_menuText = value;
RaisePropertyChanged("MenuText");
}
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
_isEnabled = value;
RaisePropertyChanged("IsEnabled");
}
}
public ICommand Command
{
get { return _command; }
set
{
_command = value;
RaisePropertyChanged("Command");
}
}
public IList<Menu> Children
{
get { return _children; }
set
{
_children = value;
}
}
#endregion
#region [INotifyPropertyChanged]
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
XAML:
<Menu Grid.Row ="0" IsMainMenu="True" x:Name="mainMenu" VerticalAlignment="Top" ItemsSource="{Binding Children}">
<Menu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<!--Or can be the line below, both yield the same result-->
<!--<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource MetroMenuItem}">-->
<!--NOTICE THAT SUB MENU's of OPEN work fine-->
<Setter Property="Header" Value="{Binding Path=MenuText}"/>
<Setter Property="Command" Value="{Binding Path=Command}"/>
<Setter Property="ItemsSource" Value="{Binding Path=Children}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>

I think I found an answer here
http://karlshifflett.wordpress.com/2008/02/03/wpf-sample-series-databound-hierarchicaldatatemplate-menu-sample/
It does the binding correctly - along with maintaining Separators.

Related

Unable to set Custom TreeView's Selected Item in Treeview programmtically

I created a WPF application where I create a list of items to be executed, as a Treeview. On a click event, I parse the ObservableCollection items one by one. This observableCollection is set as the DataContext for the treeview. When running the tests, I want to highlight the current running item in the Treeview.
I have the implemented following code, but the highlighting on the Treeview (visuallY) does not seem to happen. I checked that the "IsSelected" property does get set/unset as programmed.
I am not sure were I went wrong. Could you point out where the mistake is.
I have this class used as a DataContext to the TreeView (named mainTree).
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
bool? _isSelected = false;
public bool? IsSelected
{
get { return _isSelected; }
set { SetIsSelected(value); }
}
void SetIsSelected(bool? val)
{
_isSelected = val;
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML file is
<Grid.Resources>
<ResourceDictionary>
<HierarchicalDataTemplate x:Key="tvTemplate" ItemsSource="{Binding children, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding _name, Mode=TwoWay}" Margin="2,0" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Grid.Resources>
<TreeView x:Name="mainTree" Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" Background="WhiteSmoke"
Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="1,0,2,0" SelectedItemChanged="mainTree_SelectedItemChanged"
ItemTemplate="{StaticResource tvTemplate}"
ItemsSource="{Binding}" DataContext="{Binding nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="FontWeight" Value="Normal" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And my MainWindow code is:
public partial class MainWindow : Window
{
ObservableCollection<mytreefile> nodes = new ObservableCollection<mytreefile>();
mytreefile mtf = null;
Thread thThread = null;
int gnCount = 0;
private void LoadTree ()
{
mytreefile tf1 = new mytreefile("Group1");
nodes.Add(tf1);
mytreefile subtf1 = new mytreefile("Sub Item 1");
mytreefile subtf2 = new mytreefile("Sub Item 2");
mytreefile subtf3 = new mytreefile("Sub Item 3");
mytreefile subtf4 = new mytreefile("Sub Item 4");
tf1.children.Add(subtf1); tf1.children.Add(subtf2); tf1.children.Add(subtf3); tf1.children.Add(subtf4);
maintree.DataContext = nodes;
}
private void OnButton1_click()
{
mtf = nodes.ElementAt(0);
gnCount = 0;
thThread = new Thread(new ThreadStart(this.myThread));
thThread.Start();
}
public void myThread ()
{
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send,
new Action(() => SetTreeItem(i)));
}
}
public void SetTreeItem(int i)
{
if (gnCount > 0) {
mytreefile mtreeitem = mtf.children.ElementAt(gnCount-1);
mtreeitem.IsSelected = false;
}
mytreefile mtreeitem = mtf.children.ElementAt(gnCount++);
mtreeitem.IsSelected = true;
}
}
The problem was with the "mytreefile" class.
The below class works fine. The way the "IsSelected" implementation was done made the difference. Posting the code for reference.
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != this._isSelected)
{
this._isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

WPF MVVM view not updated when viewmodels properties are

Context :
I have a treeview with a separate details view injecting by PRISM library when I click on one of my treeviewitem (and I can update all properties of my item with it). All my items have a Enabled property.
Problem :
When I update programmatically my viewmodels property, my object is updated. If I click on an other treeviewitem and come back to the first one, I see the property was updated.
All the updates are good when I enable/disable the item using my details view (the foreground is going grey and the property is change)
But in my case, when I try to update it by a command triggered by a contextMenu it doesn't trigger the view and all the updates... but my viewmodel property is updated...
What am I going wrong ?
I am using ObservableCollection in my treeview, maybe I need to change the type of my collection ?
I have my BaseViewModel who implements NotifyPropertyChanged
public abstract class NotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(Expression<Func<object>> propertyExpression)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(GetPropertyName(propertyExpression)));
}
private string GetPropertyName(Expression<Func<object>> propertyExpression)
{
var unaryExpression = propertyExpression.Body as UnaryExpression;
var memberExpression = unaryExpression == null ? (MemberExpression)propertyExpression.Body : (MemberExpression)unaryExpression.Operand;
var propertyName = memberExpression.Member.Name;
return propertyName;
}
}
So I call the property change method, but Why my view is not updating then ?
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool Enabled
{
get
{
return Model.Enabled;
}
set
{
if (value != Model.Enabled)
{
Model.Enabled = value;
OnPropertyChanged(() => Model.Enabled);
}
}
}
Here is the code of my view (for the command)
<MenuItem Header="Enable/Disable this equipment" Command="{Binding PlacementTarget.Tag.DataContext.ToogleEquipmentCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ContextMenu}}"
CommandParameter="{Binding}" InputGestureText="CTRL+D"/>
And here is the code of my view (Hierarchical data template from my treeview)
<!-- ModuleItems > IP / Name -->
<HierarchicalDataTemplate DataType="{x:Type siemens:ModuleItemSiemensViewModel}" >
<StackPanel Orientation="Horizontal">
<TextBlock Name="ItemIp"
Text="{Binding Path=Ip}" ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text=" / " ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Name="ItemName" ContextMenu="{StaticResource ContextMenuEquipment}" Tag="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
Text="{Binding Path=Name}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Enabled}" Value="False">
<Setter Property="Background" Value="LightGray"/>
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
<DataTrigger Binding="{Binding Enabled}" Value="True">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
EDIT :
Here is the code from my viewmodel and models :
My real problem is when I update an Item (with my property enabled) it update the item, but my list (ModuleItems) is not updated, what I need to do to correctly implement MVVM and to make my fields automatically updated ?
public class ModuleParamSiemensViewModel : ModuleParamBaseViewModel
{
#region Attributes
private ObservableCollection<ModuleItemSiemensViewModel> _moduleItems;
private ModuleParamSiemens _model;
private string _moduleType;
#endregion
#region Constructor
public ModuleParamSiemensViewModel(ModuleParamSiemens moduleParam) : base(moduleParam)
{
this.Model = moduleParam;
this.ModuleType = "Siemens";
ModuleItems = new ObservableCollection<ModuleItemSiemensViewModel>();
Initialize();
}
#endregion
#region Properties
public new ModuleParamSiemens Model
{
get
{
return _model;
}
set
{
if (value != _model)
{
_model = value;
OnPropertyChanged(() => Model);
}
}
}
public new ObservableCollection<ModuleItemSiemensViewModel> ModuleItems
{
get
{
return _moduleItems;
}
set
{
this._moduleItems = value;
OnPropertyChanged(() => ModuleItems);
}
}
public override string ModuleType
{
get
{
return _moduleType;
}
set
{
this._moduleType = value;
OnPropertyChanged(() => ModuleType);
}
}
#endregion
#region Public Methods
public void Initialize()
{
foreach (ModuleItemSiemens item in this.Model.ModuleItems)
{
Add(new ModuleItemSiemensViewModel(item));
}
}
public void Add(ModuleItemSiemensViewModel item)
{
ModuleItems.Add(item);
}
#endregion
}
Model :
public class ModuleParamSiemens : ModuleParam
{
public new ObservableCollection<ModuleItemSiemens> ModuleItems { get; set; }
public ModuleParamSiemens()
{
ModuleItems = new ObservableCollection<ModuleItemSiemens>();
}
}
EDIT 2 :
Add ItemSiemensViewModel
public class ItemSiemensViewModel : ItemBaseViewModel
{
#region Attributes
private ItemSiemens _model;
#endregion
#region Constructor
public ItemSiemensViewModel(ItemSiemens item)
{
this.Model = item;
}
#endregion
#region Properties
public new ItemSiemens Model
{
get
{
return _model;
}
set
{
if (value != _model)
{
_model = value;
OnPropertyChanged(() => Model);
}
}
}
public new OPCInfo Opc
{
get
{
return Model.Opc;
}
set
{
if (value != Model.Opc)
{
Model.Opc = value;
OnPropertyChanged(() => Model.Opc);
}
}
}
public ProtocolInfoSiemens Protocol
{
get
{
return Model.Protocol;
}
set
{
if (value != Model.Protocol)
{
Model.Protocol = value;
OnPropertyChanged(() => Model.Protocol);
}
}
}
#endregion
#region Public Methods
#endregion
}
ItemSiemens :
public class ItemSiemens : Item
{
public ProtocolInfoSiemens Protocol { get; set; }
}
ItemBaseViewModel
public abstract class ItemBaseViewModel : BaseViewModel
{
public OPCInfoBaseViewModel Opc { get; set; }
public ItemBaseViewModel()
{
}
}
Item
public abstract class Item
{
public OPCInfo Opc { get; set; }
}
I have found the answer.
My bindings are correct (or at least it works)
The problem is that I used ObservableCollection collection and when an item is update in this collection it's not even fire an event to say that something has changed (it does for adding and removing items)
So I have implemented my own ItemsChangeObservableCollection (you can look this answer : https://stackoverflow.com/a/33866549/8237280)
And now all my problems in all my app are solved !
You are sending INotifyPropertyChanged on your ModuleItemSiemensViewModel for the property Model.Enabled. This does not make much sense, as nobody is listening on the VM (ModuleItemSiemensViewModel) for this change. The INPC interface does not allow such a kind of update. Every control listens on the same object that it's binding a property. That means you can only send PropertyChanged for properties that are in the same class/instance the interface is declared.
You have to move the NotifyPropertyChanged to the "Model" instance and call it there like this:
[DefaultValue(true)]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
public bool Enabled
{
get
{
return Model.Enabled;
}
set
{
if (value != Model.Enabled)
{
Model.Enabled = value;
Model.OnPropertyChanged(() => Enabled);
}
}
}

Dynamic Tab Control which can hold User Controls

I want to create a Tab Control which can hold for multiple User Controls.
<TabControl Padding="0">
<TabItem Header="{x:Static p:Resources.Scheduler}"
Visibility="{Binding ShellService.IsSchedulerEnabled,
Converter={StaticResource BoolToVisibilityConverter}}">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Content"
Value="{Binding ShellService.LazySchedulerView.Value}"/>
</Trigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</TabItem>
</TabControl>
The xaml is only for 1 tab item, which control by ShellService.IsSchedulerEnabled and the content is ShellService.LazySchedulerView.Value.
My problem here is that if I want to create a new TabItem, I have to create a new TabItem tag in the xaml.
How can I create a dynamic tab control to hold more than 1 tab item without specifying 'Value' in ContentControl.
public interface IShellService : INotifyPropertyChanged
{
object ShellView { get; }
bool IsSchedulerEnabled { get; set; }
Lazy<object> LazySchedulerView { get; set; }
}
[Export(typeof(IShellService)), Export]
internal class ShellService : Model, IShellService
{
private object _shellView;
private bool _isSchedulerEnabled;
private Lazy<object> _lazySchedulerView;
public object ShellView
{
get { return _shellView; }
set { SetProperty(ref _shellView, value); }
}
public bool IsSchedulerEnabled
{
get { return _isSchedulerEnabled; }
set { SetProperty(ref _isSchedulerEnabled, value); }
}
public Lazy<object> LazySchedulerView
{
get { return _lazySchedulerView; }
set { SetProperty(ref _lazySchedulerView, value); }
}
}
You can use Style for this TabItem. I created some example for you. You should change Bindings to your own. And you should create ObservableCollection of ShellServices and bind it to the TabControl. I hope this helps.
<TabControl ItemsSource="{Binding Objects}">
<TabControl.Resources>
<Style TargetType="TabItem" x:Key="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Header}"></Setter>
<Style.Triggers>
<Trigger Property="IsVisible" Value="True">
<Setter Property="Content" Value="{Binding Text}"/>
</Trigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
</TabControl>
Update
ViewModel Sample
public class OwnObject : ViewModelBase
{
private string _text;
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged( "Text" ); }
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void NotifyPropertyChanged( String info )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( info ) );
}
}
}
I bound these objects to TabControl.
private ObservableCollection<OwnObject> _objects = new ObservableCollection<OwnObject>();
public ObservableCollection<OwnObject> Objects
{
get { return _objects; }
set { _objects = value; NotifyPropertyChanged( "Objects" ); }
}

wpf fire PropertyChanged event in binding source that binding gets updated

I'm trying to make some CustomControl Textboxes with validation features.
(for example a number only textbox or a zip-code textbox etc.)
It should realized in a .dll library file.
My project contains a CustomControl for the textbox, a class wich handles the validations,
and a ErrMsgGui CustomControl that should show a error message in a TextBlock
(exmp.: Only numbers allowed...)
My problem is that I don't get the TextBlock Text updated when a method in the validation class is called
Is there a way to trigger the PropertyChangeEvent which updates the Textblock text within the validaiton class?
(Im quite new to wpf)
Generic.xaml:
<Style TargetType="{x:Type local:NumTb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:NumTb}">
<TextBox Background="{TemplateBinding Background}" Text="{Binding Source={StaticResource NumTbVm}, Path=NumTbText, UpdateSourceTrigger=PropertyChanged}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ErrMsgGui}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ErrMsgGui}">
<TextBlock Text="{ Binding Source={StaticResource val}, Path=ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Validations.cs:
private const string ONLY_NUMBERS_REGEX = #"^[0-9]+$"; //Nur Zahlen
private string _ErrMsgGuiText;
public string ErrMsgGuiText
{
get { return _ErrMsgGuiText; }
set
{
_ErrMsgGuiText = value;
Debug.Print("QueryText: " + value);
OnPropertyChanged("ErrMsgGuiText");
}
}
public object[] onlyNumbers(string s2c, bool output)
{
object[] objRes = new object[2];
bool result = true;
string errMsg = "";
Regex regex = new Regex(ONLY_NUMBERS_REGEX);
if (s2c != null && s2c != "" && !regex.IsMatch(s2c))
{
result = false;
errMsg = "Nur Zahlen sind zulässig";
}
objRes[0] = result;
objRes[1] = errMsg;
if (output == true)
{
ErrMsgGuiText = errMsg;
}
return objRes;
}
public void onlyNumbers(string s2c)
{
onlyNumbers(s2c, true);
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
NumTbViewModel.cs:
Validations val = null;
public NumTbViewModel()
{
val = new Validations();
}
private string _NumTbText;
public string NumTbText
{
get { return _NumTbText; }
set
{
_NumTbText = value;
this.OnPropertyChanged("NumTbText");
val.onlyNumbers(_NumTbText);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
It looks like the TextBlock source is looking at a static resource for the Validations class and the Validations called in your NumTbViewModel is NOT the same as the static resource. A solution could be to add a property to NumTbViewModel.cs and point your binding to that property so the Validations class instances will be the same. In NumTbViewModel.cs add:
Validations _val;
public Validations Val
{
get { return _val; }
set
{
_val = value;
this.OnPropertyChanged("Val");
}
}
Change your source and path in xaml binding on the TextBlock:
<TextBlock Text="{ Binding Source={StaticResource NumTbVm}, Path=Val.ErrMsgGuiText, UpdateSourceTrigger=PropertyChanged}" Background="{TemplateBinding Background}"/>
Another way:
You could also set the Val property of your NumTbViewModel when you define your static resource like so:
<local:Validations x:Key="val" />
<local:NumTbViewModel x:Key="NumTbVm" Val="{StaticResource val}" />
Doing this you can keep the bindings like you originally had.

ListView not refreshing after raising PropertyChangedEvent on ViewModel

I am trying to create a File Explorer style TreeView/ListView window that will allow the user to select a "Project" from within a "ProjectFolder".
The "ProjectFolder" tree is bound to a property on my ViewModel called "RootProjectFolders" and the "Project" list is bound to a property called "ProjectsInSelectedFolder". Things were mostly working; however, I was getting null exceptions when I first loaded the window because the "SelectedFolder" had not yet been set. When I tried to implement a simple check to make sure that the "SelectedFolder" was not null, my "Project" ListView stopped refreshing.
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
If I remove (this.SelectedFolder != null) from the above, the ListView will update, but I will get an NullException error. Why is that check breaking my binding?
Following up on the request for additional information, here is the XAML of the TreeView and ListView that are binding to the properties on the ViewModel:
<TreeView Name="treeviewProjectFolders" Grid.Column="0"
ItemsSource="{Binding Path=RootProjectFolders}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<GridSplitter Name="splitterProjects" Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<ListView Name="listviewProjects" Grid.Column="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Path=ProjectsInSelectedFolder}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And here is the ViewModel
public class SelectProjectViewModel : ViewModelBase
{
#region Fields
List<ProjectViewModel> _projectsInSelectedFolder;
List<ProjectFolderViewModel> _rootProjectFolders;
static ProjectFolderViewModel _selectedFolder = null;
ProjectViewModel _selectedProject;
#endregion // Fields
#region Constructor
public SelectProjectViewModel(ProjectFolders rootProjectFolders)
{
if (_rootProjectFolders != null) { _rootProjectFolders.Clear(); }
_rootProjectFolders = new List<ProjectFolderViewModel>();
foreach (ProjectFolder rootFolder in rootProjectFolders)
{
_rootProjectFolders.Add(new ProjectFolderViewModel(rootFolder, this));
}
_projectsInSelectedFolder = new List<ProjectViewModel>();
// Subscribe to events
this.PropertyChanged += OnPropertyChanged;
}
#endregion // Constructor
#region Properties
public List<ProjectFolderViewModel> RootProjectFolders
{
get
{
return _rootProjectFolders;
}
}
public List<ProjectViewModel> ProjectsInSelectedFolder
{
get
{
return _projectsInSelectedFolder;
}
}
public ProjectFolderViewModel SelectedFolder
{
get
{
return _selectedFolder;
}
set
{
if (_selectedFolder != value)
{
_selectedFolder = value;
}
}
}
public ProjectViewModel SelectedProject
{
get
{
return _selectedProject;
}
set
{
_selectedProject = value;
base.RaisePropertyChangedEvent("SelectedProject");
}
}
#endregion // Properties
#region Methods
public void FindSelectedFolder(ProjectFolderViewModel root)
{
if (root.IsSelected) { _selectedFolder = root; }
else
{
foreach (ProjectFolderViewModel folder in root.Children)
{
if (_selectedFolder == null)
{
FindSelectedFolder(folder);
}
}
}
}
#endregion // Methods
#region Event Handlers
void OnPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case "SelectedFolder":
_selectedFolder = null;
foreach (ProjectFolderViewModel root in this.RootProjectFolders)
{
if (_selectedFolder == null)
{
this.FindSelectedFolder(root);
}
}
_projectsInSelectedFolder.Clear();
if ((this.SelectedFolder != null) && (this.SelectedFolder.ProjectFolder.Projects != null))
{
foreach (Project project in this.SelectedFolder.ProjectFolder.Projects)
{
_projectsInSelectedFolder.Add(new ProjectViewModel(project));
}
}
base.RaisePropertyChangedEvent("ProjectsInSelectedFolder");
break;
}
}
#endregion // Event Handlers
Also, here is the ViewModel for the individual project folders that are used to raise the "SelectedFolder" property:
public class ProjectFolderViewModel : ViewModelBase
{
#region Fields
ReadOnlyCollection<ProjectFolderViewModel> _children;
List<ProjectFolderViewModel> _childrenList;
bool _isExpanded;
bool _isSelected;
ProjectFolderViewModel _parentNode;
SelectProjectViewModel _parentTree;
ProjectFolder _projectFolder;
#endregion // Fields
#region Constructor
public ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree) : this(projectFolder, parentTree, null)
{ }
private ProjectFolderViewModel(ProjectFolder projectFolder, SelectProjectViewModel parentTree, ProjectFolderViewModel parentNode)
{
_projectFolder = projectFolder;
_parentTree = parentTree;
_parentNode = parentNode;
_childrenList = new List<ProjectFolderViewModel>();
foreach (ProjectFolder child in _projectFolder.ChildFolders)
{
_childrenList.Add(new ProjectFolderViewModel(child, _parentTree));
}
_children = new ReadOnlyCollection<ProjectFolderViewModel>(_childrenList);
}
#endregion // Constructor
#region Properties
public ReadOnlyCollection<ProjectFolderViewModel> Children
{
get
{
return _children;
}
}
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
if (value != _isExpanded)
{
_isExpanded = value;
this.OnPropertyChanged("IsExpanded");
}
// Expand all the way up to the root.
if (_isExpanded && _parentNode != null)
_parentNode.IsExpanded = true;
}
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
base.RaisePropertyChangedEvent("IsSelected");
//if (_isSelected)
//{
_parentTree.RaisePropertyChangedEvent("SelectedFolder");
//}
}
}
public string Name
{
get
{
return _projectFolder.Name;
}
}
public ProjectFolder ProjectFolder
{
get
{
return _projectFolder;
}
}
#endregion // Properties
Change all your
List<T> to observablecollection<T>
because when ever there is new file or folder your adding the Item, your not creating new List, since observablecollection implements INotifyCollectionChanged, and INotifyPropertyChanged it'll internally take care of notifying and refreshing the View. But list cant do that

Categories

Resources