Problems with TreeView and Prism-5 - c#

I'm having quite a lot of problems trying to build a TreeView using Prism and MVVM. I have my application divided in Regions one of this regions have a module with a TreeView and another region has as module a Ribbon where some kind of projects can be created and configured. Those regions are communicated using prism and that works perfectly. So when a project is created in the "Ribbon Module", the "TreeView Module" gets it and here is where the problem comes.
The ViewModel of the "TreeView Module" has an ObservableCollection where all project should be added. The name of the project and other properties of this class should be showed in the TreeView.
public class Project : BindableBase
{
private List<DataSet> _DataSetList;
private string _projectName;
public Project()
{
DataSetList = new List<DataSet>();
ProjectName = "";
}
public Project(string projectName, List<DataSet> dataSets)
{
ProjectName = projectName;
DataSetList = dataSets;
}
public string ProjectName
{
get { return _projectName; }
set { SetProperty(ref this._projectName, value); }
}
public List<DataSet> DataSetList
{
get { return _DataSetList; }
set { SetProperty(ref this._DataSetList, value); }
}
public bool CheckForLoadedDataSets()
{
foreach (DataSet ds in DataSetList)
{
if(ds.Status != DataSet.DataSetStatus.Loaded)
{
return false;
}
}
return true;
}
}
This is what a DataSet is
public class DataSet : BindableBase
{
public enum DataSetStatus
{
Loaded,
Stopped,
Unloaded,
Empty,
LoadingData,
CorruptData,
};
private string _dataSetSource;
private DataSetStatus _status;
public DataSet(string name, string sourceName, List<Injector> injertors)
{
DataSetName = name;
DataSetSource = sourceName;
Injectors = injertors;
Status = DataSetStatus.Empty;
}
public DataSet(string name, string sourceName)
{
DataSetName = name;
DataSetSource = sourceName;
Injectors = new List<Injector>();
Status = DataSetStatus.Empty;
}
public DataSet(string sourceName)
{
DataSetName = "";
DataSetSource = sourceName;
Injectors = new List<Injector>();
Status = DataSetStatus.Empty;
}
public string DataSetName { get; set; }
public string DataSetSource { get { return _dataSetSource; } set { SetProperty(ref this._dataSetSource, value); } }
public List<Injector> Injectors { get; set; }
public DataSetStatus Status { get { return _status;} set{ SetProperty(ref this._status,value);} }
}
This is the TreeViewViewModel
class TreeProjectManagerViewModel : BindableBase, INavigationAware
{
private ObservableCollection<Project> _projectCollection;
public ObservableCollection<Project> Projects
{
get { return this._projectCollection; }
set
{
SetProperty(ref this._projectCollection, value);
}
}
public TreeProjectManagerViewModel()
{
Projects = new ObservableCollection<Project>();
}
/// <summary>
/// Checking parameters
/// </summary>
/// <param name="navigationContext"></param>
/// <returns></returns>
public bool IsNavigationTarget(NavigationContext navigationContext)
{
if (navigationContext.Parameters["ProjectObject"] != null)
{
return true;
}
return false;
}
public void OnNavigatedFrom(NavigationContext navigationContext)
{
}
/// <summary>
/// Getting information from other module
/// </summary>
/// <param name="navigationContext"></param>
public void OnNavigatedTo(NavigationContext navigationContext)
{
Project p = (Project)navigationContext.Parameters["ProjectObject"];
Projects.Add(p);
}
}
And here the code in the View
<Grid>
<TreeView ItemsSource="{Binding Projects}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Projects}">
<TextBlock Text="{Binding Path=ProjectName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
With this I'm able to get the name of the Project in my TreeView but for example, an here my questions:
1.- how can I get a second hierarchy of TreeNodes with the name of each DataSet?
2.- how can I manage the events in my TreeView?
Thank you very much in advance.

Try something like that:
<TreeView ItemsSource="{Binding Projects}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ie:CallMethodAction MethodName="OnTreeviewLoaded" TargetObject="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:Project}" ItemsSource="{Binding Path=DataSetList}">
<TextBlock Text="{Binding Path=ProjectName}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type model:DataSet}">
<TextBlock Text="{Binding Path=DataSetName}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
you also need to add :
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ie="http://schemas.microsoft.com/expression/2010/interactions"
into your xaml and create the event handler in your view model

Related

C# WPF MVVM TreeViewItem ToggleButton NEVER shows up

I have recently been working with TreeView to represent files on an SFTP server. The way things load, it gets directories first and adds those to the TreeView. Afterwards, it loops through the directories and and then populates the files.
The Model is as follows.
public class FileData : ViewModelBase
{
private string _Display = "";
public string Display
{
get
{
return _Display;
}
set
{
_Display = value;
OnPropertyChanged(new PropertyChangedEventArgs("Display"));
}
}
}
public class AlbumData : ViewModelBase
{
public AlbumData(string albumName, List<FileData> albumFileList)
{
if (string.IsNullOrEmpty(albumName) || albumFileList == null)
{
return;
}
AlbumName = albumName;
AlbumFiles = albumFileList;
}
public string AlbumName { get; set; } = "";
private List<FileData> _AlbumFiles = new List<FileData>();
public List<FileData> AlbumFiles
{
get
{
return _AlbumFiles;
}
set
{
_AlbumFiles = value;
OnPropertyChanged(new PropertyChangedEventArgs("AlbumFiles"));
}
}
}
Then in the XAML:
<TreeView x:Name="ftpFilesTreeView" ItemsSource="{Binding ServerAlbums}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding AlbumFiles}" DataType="{x:Type local:AlbumData}">
<TextBlock Text="{Binding AlbumName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:FileData}">
<TextBlock Text="{Binding Display}"/>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Then I have my ObservableCollection with the data for the TreeView.
private ObservableCollection<AlbumData> _ServerAlbums = new ObservableCollection<AlbumData>();
public ObservableCollection<AlbumData> ServerAlbums
{
get { return _ServerAlbums; }
set
{
_ServerAlbums = value;
OnPropertyChanged(new PropertyChangedEventArgs("ServerAlbums"));
}
}
So, my big issue here is the ToggleButton is not showing up after items are getting added to the TreeView.
I have tried using Blend to create a custom TreeViewItem and tinkering with the HasItems property. I think the issue is HasItems is not getting updated as objects are getting added to to my ServerAlbums[X].AlbumFiles collection.
I was thinking I could do a gross hack and just manually set HasItems when stuff is added, but I feel there needs to be a more elegant solution.
Thanks to a comment from #ASh, I was able to see that I was using a List instead of the correct ObservableCollection.
public class AlbumData : ViewModelBase
{
public AlbumData(string albumName, ObservableCollection<FileData> albumFileList)
{
if (string.IsNullOrEmpty(albumName) || albumFileList == null)
{
return;
}
AlbumName = albumName;
AlbumFiles = albumFileList;
}
public string AlbumName { get; set; } = "";
private ObservableCollection<FileData> _AlbumFiles = new ObservableCollection<FileData>();
public ObservableCollection<FileData> AlbumFiles
{
get
{
return _AlbumFiles;
}
set
{
_AlbumFiles = value;
OnPropertyChanged(new PropertyChangedEventArgs("AlbumFiles"));
}
}
}

Tree View Hierarchical DataTemplate Binding - MVVM

Here I'm trying to bind 'Solution' List to TreeView. Each 'Solution' has 'File' List and 'Solution Name'. I want to use Hierarchical DataTemplate to do this. In Debug mode I checked that 'Solution' List and 'File' list are successfully set. But in my view there is nothing shown.
In addition, in my view class when I try to set Data Type of the Hierarchical DataTemplate, it says "SolutionExplorerModel" does not exist int the namespace even though it does.
ViewModel
public class SolutionExplorerViewModel : INotifyPropertyChanged
{
private List<SolutionExplorerModel> _solutions = new List<SolutionExplorerModel>();
public List<SolutionExplorerModel> Solutions
{
get { return _solutions; }
set
{
_solutions = value;
RaisePropertyChanged("Solutions");
}
}
public SolutionExplorerViewModel()
{
Messenger.Default.Register<OpenFileDialog>(this, OnItemReceived);
}
private void OnItemReceived(OpenFileDialog openFile)
{
var solutionName = openFile.SafeFileName.Replace(".psim", "");
var files = new List<FileModel>();
var solutionPath = openFile.FileName.Replace(openFile.SafeFileName, "");
foreach(var file in Directory.EnumerateFiles(solutionPath, "*.xml"))
{
files.Add(new FileModel(file));
}
var newSolution = new SolutionExplorerModel
{
SolutionName = solutionName,
Files = files
};
_solutions.Add(newSolution);
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyThatChanged)
{
//checking if event is not null than raise event and pass
//in propperty name that has changed
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyThatChanged));
}
}
SolutionExplorerModel
public class SolutionExplorerModel : INotifyPropertyChanged
{
private string _solutionName;
public string SolutionName
{
get { return _solutionName; }
set
{
_solutionName = value;
RaisePropertyChanged("SolutionName");
}
}
private List<FileModel> _files;
public List<FileModel> Files
{
get { return _files; }
set
{
_files = value;
RaisePropertyChanged("Files");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyThatChanged)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyThatChanged));
}
}
FileModel
public class FileModel : INotifyPropertyChanged
{
private string _safeName;
public string SafeName
{
get { return _safeName; }
set
{
_safeName = value;
RaisePropertyChanged("SafeName");
}
}
private string _path;
public string Path
{
get { return _path; }
set
{
_path = value;
RaisePropertyChanged("Path");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyThatChanged)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyThatChanged));
}
public FileModel(string path)
{
this.Path = path;
this.SafeName = path.Split('\\').LastOrDefault();
}
}
View
<TreeView ItemsSource="{Binding Solutions}" DataContext="{Binding Source={StaticResource mainViewModelLocater}, Path=SolutionExplorerViewModel}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:SolutionExplorerModel}" ItemsSource="{Binding Files}">
<TextBlock Text="{Binding SolutionName}"></TextBlock>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
This should work provided that the SolutionExplorerViewModel property of your mainViewModelLocater actually returns a populated SolutionExplorerViewModel:
<TreeView ItemsSource="{Binding Solutions}" DataContext="{Binding Source={StaticResource mainViewModelLocater}, Path=SolutionExplorerViewModel}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type model:SolutionExplorerModel}" ItemsSource="{Binding Files}">
<TextBlock Text="{Binding SolutionName}"></TextBlock>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type model:FileModel}">
<TextBlock Text="{Binding SafeName}" />
</DataTemplate>
</TreeView.Resources>
</TreeView>
Try to set the DataContext explicitly and make sure that you populate the Solutions collection:
treeView.DataContext = new SolutionExplorerViewModel();

WPF: Filtering a dataGrid on the fly

In my WPF Window I have a DataGrid control, with its ItemsSource bound to an ObservableCollection of items (let's say a simple object with a couple properties):
XAML: (Removed some xmlns stuff for brevity)
<Window>
<Window.Resources>
<CollectionViewSource x:Key="MyViewSource"
Source="{Binding MyItemList}"
Filter="MyItemList_Filter"/>
</Window.Resources>
<Window.DataContext>
<!-- Some Ioc stuff -->
</Window.DataContext>
<StackPanel>
<TextBox Text="{Binding TextFilter}" />
<DataGrid Grid.Row="1" x:Name="dataGrid"
ItemsSource="{Binding Source={StaticResource MyViewSource}}"
SelectionUnit="FullRow"
SelectionMode="Extended"
CanUserAddRows="False"
CanUserDeleteRows="False"
HeadersVisibility="Column" />
</StackPanel>
</Window>
ViewModel (cs):
public class ViewModel : ViewModelBase // From Galasoft MVVM Light toolkit
{
#region TextFilter Property
public const string TextFilterPropertyName = "TextFilter";
private string _TextFilter;
public string TextFilter
{
get
{
return _TextFilter;
}
set
{
if (_TextFilter == value)
{
return;
}
_TextFilter = value;
RaisePropertyChanged(TextFilterPropertyName);
}
}
#endregion // TextFilter Property
#region MyItemList Property
public const string MyItemListPropertyName = "MyItemList";
private ObservableCollection<Item> _MyItemList;
public ObservableCollection<Item> MyItemList
{
get
{
return _MyItemList;
}
set
{
if (_MyItemList == value)
{
return;
}
_MyItemList = value;
RaisePropertyChanged(MyItemListPropertyName);
}
}
#endregion // MyItemList Property
}
Filter method, from Window's code behind:
private void MyItemList_Filter(object sender, FilterEventArgs e)
{
var vm = (ViewModel)this.DataContext;
var item = (Item)e.Item;
// ...Simplified...
e.Accepted = item.PropertyToCheck.Contains(vm.TextFilter);
}
Filtering is applied only when filling MyItemList: how can I make the MyItemList_Filter be called (and DataGrid items be shown/hidden accordingly) on "live" TextFilter change?
Any help would be appreciated
You could (should) move the filtering logic to the view model, e.g.:
public class ViewModel : ViewModelBase
{
public const string TextFilterPropertyName = "TextFilter";
private string _TextFilter;
public string TextFilter
{
get
{
return _TextFilter;
}
set
{
if (_TextFilter == value)
return;
_TextFilter = value;
RaisePropertyChanged(TextFilterPropertyName);
Filter();
}
}
public const string MyItemListPropertyName = "MyItemList";
private ObservableCollection<Item> _MyItemList;
public ObservableCollection<Item> MyItemList
{
get
{
return _MyItemList;
}
set
{
if (_MyItemList == value)
return;
_MyItemList = value;
RaisePropertyChanged(MyItemListPropertyName);
}
}
private ObservableCollection<Item> _filtered;
public ObservableCollection<Item> FilteredList
{
get
{
return _filtered;
}
set
{
if (_filtered == value)
return;
_filtered = value;
RaisePropertyChanged("FilteredList");
}
}
private void Filter()
{
_filtered.Clear();
foreach(var item in _MyItemList)
{
if (item.PropertyToCheck.Contains(TextFilter))
_filtered.Add(item);
}
}
}
That's where it belongs. Then you don't need to the CollectionViewSource:
<DataGrid Grid.Row="1" x:Name="dataGrid" ItemsSource="{Binding FilteredList}" ... />
This can now be achieved using the NuGet package DataGridExtensions.

DataTemplate based on DataType not working and causing other problems

I have a TabControl with its ItemsSource bound to an ObservableCollection of type object. The only reason I do this is so that I can put both Property and Tennant (Please ignore the misspelling) classes into this list. My TabControl needs to have two different tabs based off the type. Here's what I have so far:
<view:CustomTabControl ItemsSource="{Binding OpenTabs}" Grid.Column="1" x:Name="Tabs">
<view:CustomTabControl.Resources>
<DataTemplate DataType="{x:Type model:Tennant}">
<ScrollViewer>
<Grid>
<TextBlock Text="{Binding name}"/>
</Grid>
</ScrollViewer>
</DataTemplate>
<DataTemplate DataType="{x:Type model:Property}">
<ScrollViewer>
<Grid MinWidth="350">
<!-- All of the stuff that works for the Property layout in here -->
</Grid>
</ScrollViewer>
</DataTemplate>
</view:CustomTabControl.Resources>
</view:CustomTabControl>
It's working perfectly with the Property class, but not the Tennant class. Also, I have a have to ListBox's on the side. One for Properties and one for Tennants. When I add a Tennant to the tennant's list, it shows up as there, but doesn't display the name of the tennant which I have specified like this:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Margin="10,5" Text="Properties" FontSize="16"/>
<ListBox x:Name="propertiesList" ItemsSource="{Binding Properties, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:Property}">
<TextBlock Text="{Binding title}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock Margin="10,5" Text="Tennants" FontSize="16"/>
<ListBox x:Name="tennantsList" ItemsSource="{Binding Tennants, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type model:Tennant}">
<TextBlock Text="{Binding name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</ScrollViewer>
As far as I can see, the Tennant class is almost identical to the Property class, except for of course the information it stores, but it almost seems like nothing is binding properly to the Tennant object. What do I need to change? Is there something wrong with my Tennant class? Or is there something wrong in my xaml? Here is the Tennant class, paraphrased, for reference:
[Serializable]
public class Tennant : ISerializable, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool current;
private string name;
private string phone;
private string email;
private string occupation;
public bool Current { get { return current; } set { current = value; OnPropertyChanged("Current"); } }
public string Name { get { return name; } set { name = value; OnPropertyChanged("Name"); } }
public string Phone { get { return phone; } set { phone = value; OnPropertyChanged("Phone"); } }
public string Email { get { return email; } set { email = value; OnPropertyChanged("Email"); } }
public string Occupation { get { return occupation; } set { occupation = value; OnPropertyChanged("Occupation"); } }
public Tennant()
{
}
public Tennant(bool current, string name, string phone, string email, string occupation)
{
this.Current = current;
this.Name = name;
this.Phone = phone;
this.Email = email;
this.Occupation = occupation;
}
private void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
public override string ToString()
{
return "" + Current + " " + Name + " " + Phone + " " + Email + " " + Occupation;
}
public Tennant(SerializationInfo info, StreamingContext context)
{
this.Current = (bool)info.GetValue("Current", typeof(bool));
this.Name = (string)info.GetValue("Name", typeof(string));
this.Phone = (string)info.GetValue("Phone", typeof(string));
this.Email = (string)info.GetValue("Email", typeof(string));
this.Occupation = (string)info.GetValue("Occupation", typeof(string));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Current", this.Current);
info.AddValue("Name", this.Name);
info.AddValue("Phone", this.Phone);
info.AddValue("Email", this.Email);
info.AddValue("Occupation", this.Occupation);
}
}
Edit: Here's the Property Object just for reference:
public class Property : ISerializable
{
public string address { get; set; }
public string city { get; set; }
public string postcode { get; set; }
// ...
public Property()
{
// ...
}
public string toString()
{
return title;
}
public bool hasPhotos()
{
return false;
}
public bool hasTennant()
{
return false;
}
public void addTennant(Tennant tennant)
{
tennants.Add(tennant);
}
public void addPhoto(Photo photo)
{
photos.Add(photo);
}
public void addIssue(Problem issue)
{
issues.Add(issue);
}
public Property(SerializationInfo info, StreamingContext context)
{
// ...
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
// ...
}
}
What is there wrong in my code? Because after hours of searching I can't find anything. Thanks in advance.
Edit 2: I added the rest of the Tennant class. Also, my tabs are custom tabs that have a close button, and when I click on one of the tennant listboxitems next to the tabcontrol, it opens up an empty closable tab. The Property listboxitems on the side open up a full closable tab. Then, I can close the Property tabs, but not the Tennant tabs. Here's my code for adding to the OpenTabs object ObservableCollection:
// Inside the Constructor
OpenTabs = new ObservableCollection<Object>();
EventManager.RegisterClassHandler(typeof(ListBoxItem),
ListBoxItem.MouseLeftButtonDownEvent,
new RoutedEventHandler(this.OnMouseLeftButtonDown));
private void OnMouseLeftButtonDown(object sender, RoutedEventArgs e)
{
ListBoxItem selected = sender as ListBoxItem;
try
{
string t = (selected.Content as Property).title;
Property cur = selected.Content as Property;
OpenProperty(cur);
}
catch (NullReferenceException nre)
{
Tennant cur = selected.Content as Tennant;
OpenTennant(cur);
}
}
// This method opens a new tab with a Property's details in it
private void OpenProperty(Property property)
{
this.HomeView.Visibility = System.Windows.Visibility.Hidden;
this.TabbedView.Visibility = System.Windows.Visibility.Visible;
MainWindow.OpenTabs.Add(property);
this.Tabs.SelectedIndex = MainWindow.OpenTabs.IndexOf(property);
}
// This method opens a new tab with a Tennant's details in it
private void OpenTennant(Tennant tennant)
{
this.HomeView.Visibility = System.Windows.Visibility.Hidden;
this.TabbedView.Visibility = System.Windows.Visibility.Visible;
MainWindow.OpenTabs.Add(tennant);
this.Tabs.SelectedIndex = MainWindow.OpenTabs.IndexOf(tennant);
}
Hope that helps. Ask if you want more.
Edit 3: Now I've added context to the xaml, and also added INotifyPropertyChanged to the Tennant class which is updated above. I also forgot to add the fact that both `DataTemplates are separate. As in separated by a GridSplitter.
Edit 4: I'm still looking for answers to this question, as my problem still exists, even though I have implemented, to the best of my abilities every answer that has been suggested.
You need to make your Tennant class implement INotifyPropertyChanged. Otherwise, changes made within the backing class won't be reflected in the UI, which sounds like it could be your issue.
You're showing two different implicit DataTemplates for Tennant but not where it is that you're declaring them. If they're both ending up in scope together (or neither in scope) you could be getting the one that you're not expecting (or no template).

ObservableCollection not updating View

I am just starting with MVVM and have hit a hurdle that I hope someone can help me with. I am trying to create a simple View with 2 listboxes. A selection from the first listbox will populate the second list box. I have a class created that stores the information I want to bind to.
MyObject Class (Observable Object is just a base class that implements INotifyPopertyChanged)
public class MyObject : ObservableObject
{
String _name = String.Empty;
ObservableCollection<MyObject> _subcategories;
public ObservableCollection<MyObject> SubCategories
{
get { return _subcategories; }
set
{
_subcategories = value;
RaisePropertyChanged("SubCategories");
}
}
public String Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public MyObject()
{
_subcategories = new ObservableCollection<EMSMenuItem>();
}
}
In my viewmodel I have two ObservableCollections created
public ObservableCollection<EMSMenuItem> Level1MenuItems { get; set; }
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
In my constructor of the ViewModel I have:
this.Level1MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level2MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level1MenuItems = LoadEMSMenuItems("Sample.Xml");
That works fine for the Level1 items and they correctly show in the View. However I have a command that gets called when the user clicks an item in the listbox, which has the following:
Level2MenuItems = ClickedItem.SubCategories;
For some reason this does not update the UI of the second listbox. If I put a breakpoint at this location I can see that Level2MenuItems has the correct information stored in it. If I write a foreach loop and add them individually to the Level2MenuItems collection then it does display correctly.
Also as a test I added the following to the constructor:
Level2MenuItems = Level1MenuItems[0].SubCategories;
And that updated correctly.
So why would the code work as expected in the constructor, or when looping through, but not when a user clicks on an item in the listbox?
You need to raise the change notification on the Level2MenuItems property.
Instead of having
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
you need
private ObservableCollection<EMSMenuItem> _level2MenuItems;
public ObservableCollection<EMSMenuItem> Level2MenuItems
{
get { return _level2MenuItems; }
set
{
_level2MenuItems = value;
RaisePropertyChanged(nameof(Level2MenuItems));
}
}
The reason the former works in the constructor is that the Binding has not taken place yet. However since you are changing the reference via a command execute which happens after the binding you need to tell view that it changed
You need to make your poco class within the ObservableCollection implement INotifyPropertyChanged.
Example:
<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.
<ListView
DataContext="{StaticResource viewModel}"
ItemsSource="{Binding Locations}"
IsItemClickEnabled="True"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
<TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class LocationViewModel : BaseViewModel
{
ObservableCollection<Location> _locations = new ObservableCollection<Location>();
public ObservableCollection<Location> Locations
{
get
{
return _locations;
}
set
{
if (_locations != value)
{
_locations = value;
OnNotifyPropertyChanged();
}
}
}
}
public class Location : BaseViewModel
{
int _locationId = 0;
public int LocationId
{
get
{
return _locationId;
}
set
{
if (_locationId != value)
{
_locationId = value;
OnNotifyPropertyChanged();
}
}
}
string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnNotifyPropertyChanged();
}
}
}
float _latitude = 0;
public float Latitude
{
get
{
return _latitude;
}
set
{
if (_latitude != value)
{
_latitude = value;
OnNotifyPropertyChanged();
}
}
}
float _longitude = 0;
public float Longitude
{
get
{
return _longitude;
}
set
{
if (_longitude != value)
{
_longitude = value;
OnNotifyPropertyChanged();
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
}
Your Subcategories property should be read-only.

Categories

Resources