I having a WPF application in which the UI has a list box. The list box has binding of ObservableCollection. Log class implements INotifyPropertyChanged.
The list will show the continuous logging of the application. As long as the application is running. The ObservableCollection size keeps on growing. After some time I get the Out of Memory exception. I want to show the latest 1000 entries in the list control. Any suggestions on this will be of great help!!
XAML:
<DataGrid AutoGenerateColumns="False" SelectedValue="{Binding SelectedLog}" SelectionUnit="FullRow" SelectionMode="Single" Name="dataGridLogs"
ItemsSource="{Binding Path=LogList}" CanUserReorderColumns="True" CanUserResizeRows="True" CanUserDeleteRows="False" IsReadOnly="True"
CanUserAddRows="False" EnableColumnVirtualization="True" EnableRowVirtualization="True" SelectionChanged="grid_SelectionChanged">
<DataGrid.Columns>
<DataGridTextColumn Header="Time Stamp" Binding="{Binding StrTimeStamp, Mode=OneWay}" Width="Auto"/>
<DataGridTextColumn Header="Action" Binding="{Binding Action, Mode=OneWay}" Width="Auto"/>
</DataGrid>
ViewModel:
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
logList = value;
OnPropertyChanged("LogList");
}
}
model:
public class LogData : INotifyPropertyChanged
{
public LogData()
{
}
private String timestamp = string.Empty;
public String StrTimestamp
{
get
{
if (timestamp == null)
return string.Empty;
return timestamp ;
}
set
{
timestamp = value;
}
}
public string Action
{
get;set;
}
}
You could create your own size limited observable collection class. Something like this should get you started:
public class LimitedSizeObservableCollection<T> : INotifyCollectionChanged
{
private ObservableCollection<T> _collection;
private bool _ignoreChange;
public LimitedSizeObservableCollection(int capacity)
{
Capacity = capacity;
_ignoreChange = false;
_collection = new ObservableCollection<T>();
_collection.CollectionChanged += _collection_CollectionChanged;
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
public int Capacity {get;}
public void Add(T item)
{
if(_collection.Count = Capacity)
{
_ignoreChange = true;
_collection.RemoveAt(0);
_ignoreChange = false;
}
_collection.Add(item);
}
private void _collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(!_ignoreChange)
{
CollectionChanged?.Invoke(this, e);
}
}
}
Of course, you will probably have to expose some more methods, but I hope that's enough for you to get the idea.
it can be easily done by this class:
public class LimitedSizeObservableCollection<T> : ObservableCollection<T>
{
public int Capacity { get; }
public LimitedSizeObservableCollection(int capacity)
{
Capacity = capacity;
}
public new void Add(T item)
{
if (Count >= Capacity)
{
this.RemoveAt(0);
}
base.Add(item);
}
}
I found another way to limit the number of elements in the collection, without adding a "new" method that breaks compatibility with parent classes:
public class LimitedSizeObservableCollection<T> : ObservableCollection<T>
{
public int Capacity { get; set; } = 0;
protected override void InsertItem(int index, T item)
{
if (this.Capacity > 0 && this.Count >= this.Capacity)
{
throw new Exception(string.Format("The maximum number of items in the list ({0}) has been reached, unable to add further items", this.Capacity));
}
else
{
base.InsertItem(index, item);
}
}
}
If you want it should not add to the collection more than 1000 you can do this.
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
if(LogList.Count < 1001)
{
logList = value;
OnPropertyChanged("LogList");
}
}
}
or you can remove the old entries when adding new more than 1000
public ObservableCollection<LogData> LogList
{
get
{
if (logList == null)
{
logList = new ObservableCollection<LogData>();
}
return logList;
}
set
{
if(LogList.Count < 1001)
{
logList = value;
OnPropertyChanged("LogList");
}
else
{
LogList.RemoveAt(0);
logList = value;
OnPropertyChanged("LogList");
}
}
}
Related
Excuse me if I didn't formulate good the question, but don't know how to name it better...
I have a project with some ListView, binded to ObservableCollection.
When I make a right click on my ListView, to change name, or other parameter, the ListView doesn't automatically refresh until I go out, then open again.
Here is my behind code for context :
public class Contexte : INotifyPropertyChanged
{
private Affaire affaireSelectionnee;
public Affaire AffaireSelectionnee
{
get { return affaireSelectionnee; }
set
{
if (value == affaireSelectionnee) return;
affaireSelectionnee = value;
NotifyPropertyChanged("AffaireSelectionnee");
}
}
private ObservableCollection<Affaire> listeDesAffairesSelectionnees;
public ObservableCollection<Affaire> ListeDesAffairesSelectionnees
{
get { return listeDesAffairesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAffairesSelectionnees, value); }
}
private ObservableCollection<Phase> listeDesPhasesSelectionnees;
public ObservableCollection<Phase> ListeDesPhasesSelectionnees
{
get { return listeDesPhasesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesPhasesSelectionnees, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblagesSelectionnees;
public ObservableCollection<Assemblage> ListeDesAssemblagesSelectionnees
{
get { return listeDesAssemblagesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesAssemblagesSelectionnees, value); }
}
private ObservableCollection<Repere> listeDesReperesSelectionnees;
public ObservableCollection<Repere> ListeDesReperesSelectionnees
{
get { return listeDesReperesSelectionnees; }
set { NotifyPropertyChanged(ref listeDesReperesSelectionnees, value); }
}
private ObservableCollection<Affaire> listeDesAffaires;
public ObservableCollection<Affaire> ListeDesAffaires
{
get { return listeDesAffaires; }
set { NotifyPropertyChanged(ref listeDesAffaires, value); }
}
private ObservableCollection<Phase> listeDesPhases;
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value); }
}
private ObservableCollection<Assemblage> listeDesAssemblages;
public ObservableCollection<Assemblage> ListeDesAssemblages
{
get { return listeDesAssemblages; }
set { NotifyPropertyChanged(ref listeDesAssemblages, value); }
}
private ObservableCollection<Repere> listeDesReperes;
public ObservableCollection<Repere> ListeDesReperes
{
get { return listeDesReperes; }
set { NotifyPropertyChanged(ref listeDesReperes, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string nomPropriete)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(nomPropriete));
}
private bool NotifyPropertyChanged<T>(ref T variable, T valeur, [CallerMemberName] string nomPropriete = null)
{
if (object.Equals(variable, valeur)) return false;
variable = valeur;
NotifyPropertyChanged(nomPropriete);
return true;
}
}
I load the context once when loading the programm :
DBConnect DataBase = new DBConnect();
string requete = "SELECT * FROM affaire ORDER BY ID";
List<Affaire> liste = DataBase.Select_affaire(requete, true);
contexte = new Contexte { ListeDesAffaires = new ObservableCollection<Affaire>(liste), ListeDesPhases = new ObservableCollection<Phase>(), ListeDesAssemblages = new ObservableCollection<Assemblage>(), ListeDesReperes = new ObservableCollection<Repere>(), AffaireSelectionnee = new Affaire(), ListeDesAffairesSelectionnees = new ObservableCollection<Affaire>(liste), ListeDesPhasesSelectionnees = new ObservableCollection<Phase>(), ListeDesAssemblagesSelectionnees = new ObservableCollection<Assemblage>(), ListeDesReperesSelectionnees = new ObservableCollection<Repere>() };
DataContext = contexte;
Then my function that may update property :
foreach (Phase ph in contexte.ListeDesPhasesSelectionnees)
{
Phase ph_find = contexte.ListeDesPhases.First(s=>s==ph);
ph_find.Priorite = new_priorite;
}
ph_find.Priorite is well updated, as is my Observable Collection "contexte.ListeDesPhases", but no refresh is made on the ListView.
Edit : Well I could solve the problem adding a ListView1.Items.Refresh()...
I am not sure this is the most correct way(is not bidding supposed to refresh the listview automaticaly?), but for now it works
Edit2 :
My XAML code (ListView of the phase) :
<ListView x:Name="ListView2" ItemsSource="{Binding ListeDesPhases}" Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="4" MouseDoubleClick="ListView_MouseDoubleClick" GridViewColumnHeader.Click="GridViewColumnHeaderClickedHandler" SelectionChanged="ListView_SelectionChanged" >
<ListView.View>
<GridView AllowsColumnReorder="true" x:Name="GridView2">
<GridViewColumn DisplayMemberBinding="{Binding ID}" Header="ID" Width="50"/>
<GridViewColumn DisplayMemberBinding= "{Binding NomPhase}" Header="{x:Static p:Resources.Nom}" Width="200"/>
<GridViewColumn DisplayMemberBinding="{Binding IdAffaire}" Header="{x:Static p:Resources.IdAffaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding CommPhase}" Header="{x:Static p:Resources.Commentaire}" Width="100"/>
<GridViewColumn DisplayMemberBinding="{Binding Priorite}" Header="{x:Static p:Resources.Priorite}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
My Phase class :
public class Phase
{
public string NomPhase { get; set; }
public long IdAffaire { get; set; }
public string CommPhase { get; set; }
public int Priorite { get; set; }
public long ID { get; set; }
public List<Assemblage> ListAssemblages { get; set; }
public Phase()
{
this.NomPhase = "";
this.IdAffaire = 0;
this.CommPhase = "";
this.Priorite = 0;
this.ID = 0;
this.ListAssemblages = new List<Assemblage>();
}
...
}
Edit3 :
Tried to modify as indicated by Netstep, but still the same :
public ObservableCollection<Phase> ListeDesPhases
{
get { return listeDesPhases; }
set { NotifyPropertyChanged(ref listeDesPhases, value);
NotifyPropertyChanged("Priorite");
}
}
Edit 4 :
Well, I now understand that nothing was happening, I read that course http://www.wpf-tutorial.com/data-binding/responding-to-changes/ to understand it...
So example given by NetStep was the good one (just didn't understand what is the RaisePropertyChanged(() => Priorite); part? Is this due to the use of mvvmlight.net library?
public class Phase : INotifyPropertyChanged
{
private string nomPhase;
public string NomPhase
{
get { return this.nomPhase; }
set
{
if (this.nomPhase != value)
{
this.nomPhase = value;
this.NotifyPropertyChanged("NomPhase");
}
}
}
private int priorite;
public int Priorite
{
get { return this.priorite; }
set
{
if (this.priorite != value)
{
this.priorite = value;
this.NotifyPropertyChanged("Priorite");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
...//all of my other functions
}
So this is what I did and it works good now.
Then I have the following question : I have 4 different objects displayed in 4 ListView(Contract, Subcontract, Phase and Detail).
Contract is the "Mother Class", it countains parameters, but also contains a list of Subcontracts. Subcontracts contains several parameters, and a list of Phases, and each Phase contains some parameters, with a list of Details.
Each of them is displayed in a different ListView(4 ListView).
May I define 4 different ObservableCollection, or is there a way to define only one ObservableCollection for all the "tree", then bind on parameters of my ObservableCollection>?
To have the field properly updated in UI, please ensure that Phase class also implement INotifyPropertyChanged someway and call
NotifyPropertyChanged("Priorite")
in set accessor. Just using the ObservableCollection is not enough, it handles notifying only Add/Remove operations. And you right - ListView1.Items.Refresh() is a workaround in this case.
Hope this will help, otherwise please share Phase class code and your xaml code to get more clear comment/answer.
Here is the example, based on MVVM light library:
using GalaSoft.MvvmLight;
namespace WpfApp1
{
public class Phase : ViewModelBase
{
private int _priorite;
public int Priorite
{
get { return _priorite; }
set
{
_priorite = value;
RaisePropertyChanged(() => Priorite);
}
}
}
}
All the rest of code can remain unchanged. You also can inherit you Context class from ViewModelBase
When I expand TreeViewItem with ~3000 items, application will be stuck for about 5 seconds. After searching for solution, I realised that write a custom virtualizing panel is the only way but is difficult for me.
Here is xaml I'm using (simplified):
<controls:FileMapPresenter ItemsSource="{Binding RootSource, Mode=OneWay}"
ItemTemplateSelector="{StaticResource templateSelector}">
<controls:FileMapPresenter.Resources>
<DataTemplate x:Key="folder_template">
<controls:FolderDataPersenter ItemsSource="{Binding Source, Mode=OneWay}"
ItemTemplateSelector="{StaticResource templateSelector}">
<controls:FolderDataPersenter.Header>
<TextBlock Text="{Binding Name, Mode=OneWay}" />
</controls:FolderDataPersenter.Header>
</controls:FolderDataPersenter>
</DataTemplate>
<DataTemplate x:Key="file_template">
<TextBlock Text="{Binding Name, Mode=OneWay}" />
</DataTemplate>
</controls:FileMapPresenter.Resources>
</controls:FileMapPresenter>
controls:FileMapPresenter is derived from ItemsControl, and controls:FolderDataPersenter is derived from TreeViewItem.
FolderDataModel (simplified):
public class FolderData : ViewModelBase
{
public FolderData(string name)
{
this.name = name;
}
private List<FileData> files = new List<FileData>();
public List<FileData> Files
{
get { return this.files; }
}
private List<FolderData> subFolders = new List<FolderData>();
public List<FolderData> SubFolders
{
get { return this.subFolders; }
}
private string name;
public string Name
{
get { return this.name; }
}
private AutoInvokeObservableCollection<object> source =
new AutoInvokeObservableCollection<object>();
/// <summary>
/// ObservableCollection which contains all the files and subFolders
/// </summary>
public AutoInvokeObservableCollection<object> Source
{
get { return this.source; }
}
}
FileDataModel (simplified):
public class FileData
{
public FileData(string name)
{
this.name = name;
}
private string name;
public string Name
{
get { return this.name; }
}
}
in controls:FolderDataPersenter, I try to cancel expanding call and put it into asynchronous, then add a expanding progress reporter to notify UI:
public class FolderDataPersenter : TreeViewItem
{
public FolderDataPersenter()
{
VirtualizingStackPanel.SetIsVirtualizing(this, true);
VirtualizingStackPanel.SetVirtualizationMode(this, VirtualizationMode.Recycling);
this.Dispatcher.BeginInvoke(
(Action)this.setExpandingEventListener,
System.Windows.Threading.DispatcherPriority.Loaded);
}
private void setExpandingEventListener()
{
if (this.Template != null)
{
var expander = this.Template.FindName("Expander", this) as ToggleButton;
if (expander != null)
{
expander.PreviewMouseLeftButtonDown += this.onExpanderPreviewMouseLeftButtonDown;
}
}
}
private void onExpanderPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
this.Dispatcher.BeginInvoke((Action)delegate
{
this.IsExpanded = !this.IsExpanded; // no work here
}, System.Windows.Threading.DispatcherPriority.Background);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Business.TreeViewExpandingCounter.StepForward();
}
protected override void OnExpanded(RoutedEventArgs e)
{
var folder = this.DataContext as Models.FolderData;
if (folder != null)
{
if (folder.SubFolders.Count > 100)
{
Business.TreeViewExpandingCounter.Finish += this.onExpandingCounterDone;
Business.TreeViewExpandingCounter.Set(folder.SubFolders.Count);
}
}
base.OnExpanded(e);
}
private void onExpandingCounterDone()
{
Business.TreeViewExpandingCounter.Finish -= this.onExpandingCounterDone;
}
}
expanding progress reporter worked well but expanding asynchronously failed, application still stuck while expanding node.
so it seems I have returned to the first question "how to expand TreeViewItem faster?" which I have searched for half a day...
any suggestion?
New to WPF and C# from VB web forms, so sorry for this poorly structured question I will add to as needed to improve. I am trying to implement an example by adding database calls to MySQL to populate an On-Demand Tree View control. Here is the link to the sample code...
sample code
Got my db connection working and data is populating my dataset. I iterate to place in a List. But can not seem to figure out the issue with passing the List to the Class to populate the control...
public class Level1
{
public Level1(string level1Name)
{
this.Level1Name = level1Name;
}
public string Level1Name { get; private set; }
readonly List<Level2> _level2s = new List<Level2>();
public List<Level2> Level2s
{
get { return _level2s; }
}
}
I have a database class that queries the db and parses the data....
List<string> level1s = new List<string>();
DataSet ds = new DataSet();
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
level1s.Add((string)row["name"]);
}
}
**UPDATE**: Trying to return the list...
return new Level1[]
{
foreach(DataRow row in level1s)
{
// iterate here
}
};
My level1s List is properly populated, I am just drawing a blank on returning the values.
thanks,
UPDATE - I am including the ViewModel code here as well....
using BusinessLib;
namespace TreeViewWithViewModelTOC.LoadOnDemand
{
public class Level1ViewModel : TreeViewItemViewModel
{
readonly Level1 _level1;
public Level1ViewModel(Level1 level1)
: base(null, true)
{
_level1 = level1;
}
public string Level1Name
{
get { return _level1.Level1Name; }
}
protected override void LoadChildren()
{
foreach (Level2 level2 in Database.GetLevel2s(_level1))
base.Children.Add(new Level2ViewModel(level2, this));
}
}
}
Try like this below,
List<Level1> L1=new List<Level1>();
foreach(var row in level1s)
{
Level1 L=new Level1();
// L.Level1Name = row.ToString(); here add items as you need
L1.Add(L);
}
return L1.ToArray();
You should be using MVVM design pattern to solve this. There aren't many requirements listed in your questions so I will assume my own, which should lead you along the right path.
First thing is determining whether or not you're records are going to be ready/pulled at run-time--before the TreeView is rendered and if they will be changed/updated/added/removed from the structure during the lifecycle of the application. If the structure isn't going to be changed, you can continue to use List as your collection. If you're (or a user is) going to be adding/removing from the collection, ultimately changing the structure, then you need to notify the UI that a change occurred on the collection; so you would use the built in ObservableCollection for that. Here is a MVVM-purist solution, with the assumption that your data will be pulled at application startup and you will be modifying the collection:
Note: RelayCommand implementation was taken from here
Models
public class First
{
public string Name
{
get;
set;
}
public readonly List<Second> Children;
public First(string name)
{
Name = name;
Children = new List<Second>
{
new Second(1),
new Second(2),
new Second(3),
};
}
public void AddChild(Second child)
{
Children.Add(child);
ChildAdded(this, new ChildAddedEventArgs(child));
}
public EventHandler<ChildAddedEventArgs> ChildAdded;
}
public class ChildAddedEventArgs //technically, not considered a model
{
public readonly Second ChildAdded;
public ChildAddedEventArgs(Second childAdded)
{
ChildAdded = childAdded;
}
}
public class Second
{
public int Number
{
get;
set;
}
public Second(int number)
{
Number = number;
}
}
ViewModels
public class MainViewModel : INotifyPropertyChanged
{
private readonly ObservableCollection<FirstViewModel> _items;
private readonly ICommand _addFirstFirstChildCommand;
private readonly ICommand _addSecondFirstChildCommand;
private readonly ICommand _toggleExpandCollapseCommand;
private bool _firstAddedFlag;
public MainViewModel(IEnumerable<First> records)
{
_items = new ObservableCollection<FirstViewModel>();
foreach(var r in records)
{
_items.Add(new FirstViewModel(r));
}
_addFirstFirstChildCommand = new RelayCommand(param => AddFirst(), param => CanAddFirst);
_addSecondFirstChildCommand = new RelayCommand(param => AddSecond(), param => CanAddSecond);
_toggleExpandCollapseCommand = new RelayCommand(param => ExpandCollapseAll(), param =>
{
return true;
});
}
public ObservableCollection<FirstViewModel> Items
{
get
{
return _items;
}
}
public ICommand AddFirstFirstChildCommand
{
get
{
return _addFirstFirstChildCommand;
}
}
public ICommand AddSecondFirstChildCommand
{
get
{
return _addSecondFirstChildCommand;
}
}
public ICommand ToggleExpandCollapseCommand
{
get
{
return _toggleExpandCollapseCommand;
}
}
public bool CanAddFirst
{
get
{
return true;
}
}
public bool CanAddSecond
{
get
{
//Only allow second to be added if we added to first, first
return _firstAddedFlag;
}
}
public void AddFirstChild(FirstViewModel item)
{
Items.Add(item);
}
private void AddFirst()
{
_items[0].AddChild(new Second(10));
_firstAddedFlag = true;
}
private void AddSecond()
{
_items[1].AddChild(new Second(20));
}
private void ExpandCollapseAll()
{
foreach(var i in Items)
{
i.IsExpanded = !i.IsExpanded;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class FirstViewModel : INotifyPropertyChanged
{
private readonly First model;
private readonly ObservableCollection<SecondViewModel> _children;
private bool _isExpanded;
public FirstViewModel(First first)
{
_children = new ObservableCollection<SecondViewModel>();
model = first;
foreach(var s in first.Children)
{
Children.Add(new SecondViewModel(s));
}
model.ChildAdded += OnChildAdded;
}
public string FirstName
{
get
{
return model.Name;
}
set
{
model.Name = value;
NotifyPropertyChanged();
}
}
public ObservableCollection<SecondViewModel> Children
{
get
{
return _children;
}
}
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
_isExpanded = value;
NotifyPropertyChanged();
}
}
internal void AddChild(Second second)
{
model.AddChild(second);
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void OnChildAdded(object sender, ChildAddedEventArgs args)
{
if(Children != null)
{
Children.Add(new SecondViewModel(args.ChildAdded));
}
}
}
public class SecondViewModel : INotifyPropertyChanged
{
private readonly Second model;
private bool _isExpanded;
public SecondViewModel(Second second)
{
model = second;
}
public int SecondNumber
{
get
{
return model.Number;
}
set
{
model.Number = value;
NotifyPropertyChanged();
}
}
//Added property to avoid warnings in output window
public bool IsExpanded
{
get
{
return _isExpanded;
}
set
{
_isExpanded = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Model Provider
public class Database
{
public static IEnumerable<First> GetChildren()
{
List<First> firsts = new List<First>();
firsts.Add(new First("John"));
firsts.Add(new First("Roxanne"));
return firsts;
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private MainViewModel mvm;
public MainWindow()
{
var db = Database.GetChildren();
mvm = new MainViewModel(db);
InitializeComponent();
DataContext = mvm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//Do not do this, example only
var f = new First("Billy");
mvm.AddFirstChild(new FirstViewModel(f));
//Prove that the event was raised in First, FirstViewModel see & handles it, and
//the UI is updated
f.AddChild(new Second(int.MaxValue));
}
}
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
Title="MainWindow">
<Grid>
<TreeView ItemsSource="{Binding Items}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:FirstViewModel}"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding FirstName}" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:SecondViewModel}">
<TextBlock Text="{Binding SecondNumber}" />
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<StackPanel Orientation="Vertical"
VerticalAlignment="Bottom">
<StackPanel Orientation="Horizontal">
<Button Content="Add Child to first First"
Command="{Binding AddFirstFirstChildCommand}" />
<Button Content="Toggle Expand"
Command="{Binding ToggleExpandCollapseCommand}" />
<Button Content="Add Child to second First"
Command="{Binding AddSecondFirstChildCommand}" />
</StackPanel>
<Button Content="Bad Codebehind Button"
Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
this returns array of Level1 from first table in DataSet (usually there's only one table)
public void Level1[] GetLevels()
{
DataSet ds = ....
return ds.Tables[0].Rows
.Select(row => new Level1((string)row["name"]))
.ToArray();
}
if you had more than one table in the dataset, you can use this method to loop trough all tables:
public void Level1[] GetLevels()
{
DataSet ds = ....
return ds.Tables
.SelectMany(t => t.Rows)
.Select(row => new Level1((string)row["name"]))
.ToArray();
}
The second code sample does exactly the same as your code in the question.
Understanding linq is extremely useful.
I copied this ObservableDictionary implementation from a VS2013 generated project. It is a strongly typed implementation (Key is string and Value is object). However, I must be getting the bindings wrong because I don't see any data, I get a BindingExpression path error.
One thing I note is that the MapChangedEventHandler is always null, meaning that nobody every registered for the event!
1. Who or what registers for the MapChangedEventHandler?
2. What should my bindings be? I've tried several VARIOUS permutations of bindings. All to no avail, but here's the latest permutation of the code:
public class ObservableDictionary : IObservableMap<string, object>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
{
this.CollectionChange = change;
this.Key = key;
}
public CollectionChange CollectionChange { get; private set; }
public string Key { get; private set; }
}
private Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public event MapChangedEventHandler<string, object> MapChanged;
private void InvokeMapChanged(CollectionChange change, string key)
{
var eventHandler = MapChanged;
if (eventHandler != null)
{
eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
}
}
public void Add(string key, object value)
{
this._dictionary.Add(key, value);
this.InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<string, object> item)
{
this.Add(item.Key, item.Value);
}
public bool Remove(string key)
{
if (this._dictionary.Remove(key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<string, object> item)
{
object currentValue;
if (this._dictionary.TryGetValue(item.Key, out currentValue) &&
Object.Equals(item.Value, currentValue) && this._dictionary.Remove(item.Key))
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public object this[string key]
{
get
{
return this._dictionary[key];
}
set
{
this._dictionary[key] = value;
this.InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = this._dictionary.Keys.ToArray();
this._dictionary.Clear();
foreach (var key in priorKeys)
{
this.InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<string> Keys
{
get { return this._dictionary.Keys; }
}
public bool ContainsKey(string key)
{
return this._dictionary.ContainsKey(key);
}
public bool TryGetValue(string key, out object value)
{
return this._dictionary.TryGetValue(key, out value);
}
public ICollection<object> Values
{
get { return this._dictionary.Values; }
}
public bool Contains(KeyValuePair<string, object> item)
{
return this._dictionary.Contains(item);
}
public int Count
{
get { return this._dictionary.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this._dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
int arraySize = array.Length;
foreach (var pair in this._dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
And the xaml:
<custom:RESTAPHandler
x:Class="K1MobilePhone.Views.HomePageAdmin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:custom="clr-namespace:K1MobilePhone.Utilities"
xmlns:cells="clr-namespace:K1MobilePhone.Common"
xmlns:wptoolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True" Loaded="phoneApplicationPage_Loaded" Margin="0,6,0,-6">
<custom:RESTAPHandler.Resources>
<CollectionViewSource
x:Name="sd_source" Source="{Binding Path=SDSummaries, Mode=TwoWay}" />
<CollectionViewSource
x:Name="ticketsViewSource" Source="{Binding Path=Tickets, Mode=TwoWay}" />
</custom:RESTAPHandler.Resources>
And now the xaml.cs:
public sealed partial class HomePageAdmin : RESTAPHandler
{
private ObservableDictionary defaultViewModel = new ObservableDictionary();
public ObservableDictionary DefaultViewModel { get { return this.defaultViewModel; } }
public HomePageAdmin() : base()
{
this.InitializeComponent();
DataContext = this.DefaultViewModel;
this.DefaultViewModel["SDSummaries"] = new SDSummaries();
this.DefaultViewModel["Tickets"] = new RecentlyViewedTickets();
}
Okay I wasn't able to ever figure out why I couldn't access string-based keys in my xaml code. The default Windows store app works great. What I wound up doing is this:
Page.xaml:
<custom:RESTAPHandler.Resources>
<CollectionViewSource
x:Name="sd_source"/>
<CollectionViewSource
x:Name="ticketsViewSource"/>
</custom:RESTAPHandler.Resources>
I built everything up in the code-behind:
public sealed partial class HomePageAdmin : RESTAPHandler
{
private Grid visibleGrid;
private SDSummaries _sdSummaries = new SDSummaries();
public SDSummaries SDSummaries { get { return this._sdSummaries; } }
public HomePageAdmin() : base()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.sd_source.Source = this.SDSummaries;
base.OnNavigatedTo(e);
}
private void phoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
this.Address = this.NavigationContext.QueryString["URL"];
}
private void TicketsStatsResponse (TicketsStatsResponse response)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
PlainTitleDisclosureCellItem cell = (PlainTitleDisclosureCellItem)this.SDSummaries[1];
cell.Label = response.NewTicketCount.ToString();
cell = (PlainTitleDisclosureCellItem)this.SDSummaries[2];
cell.Label = response.UnassignedTicketCount.ToString();
});
}
Seems a bit of a hack, but I'm not sure.
I am developing windows 8 store app. I wants to show the previously selected items in GridView if navigate back and fro, the selected items should be shown selected.I have tried This tutorial
and did exactly as suggested. but its not working in my case. I have also tried with index as
int index = myGridView.SelectedIndex
so that to find index and directly provide
myGridView.SelectedIndex = index ;
but its again not useful because I am not getting changes into the index in
SelectionChanged(object sender, SelectionChangedEventArgs e){};
What works is
myGridView.SelectAll();
it selects all the elements. but I don't want this. Please help me? Thanks in advance
Please refer my code
<GridView x:Name="MyList" HorizontalAlignment="Left" VerticalAlignment="Top" Width="auto" Padding="0" Height="600" Margin="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple" SelectionChanged="names_SelectionChanged" ItemClick="mylist_ItemClick" SelectedItem="{Binding Path=selectedItem}">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Width="260" Height="80">
<TextBlock Text="{Binding Path=Name}" Foreground="White" d:LayoutOverrides="Width" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This is The class I am dealing with
public sealed partial class MyClass: MyApp.Common.LayoutAwarePage, INotifyPropertyChanged
{
SQLite.SQLiteAsyncConnection db;
public MyClass()
{
this.InitializeComponent();
Constants.sourceColl = new ObservableCollection<MyModel>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
getData();
foreach (MyModel item in Constants.sourceColl)
MyList.SelectedItems.Add(item);
}
private async void getData()
{
List<MyModel> mod = new List<MyModel>();
var query = await db.Table<MyModel>().Where(ch => ch.Id_Manga == StoryNumber).ToListAsync();
foreach (var _name in query)
{
var myModel = new MyModel()
{
Name = _name.Name
};
mod.Add(myModel);
Constants.sourceColl.Add(myModel);
}
MyList.ItemsSource = mod;
}
private void names_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
GridView myGridView = sender as GridView;
if (myGridView == null) return;
Constants.sourceColl = (ObservableCollection<MyModel>)myGridView.SelectedItems;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyModel _selectedItem;
public MyModel selectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
NotifyPropertyChanged("selectedItem");
}
}
}
}
Here is my model
class MyModel
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public String Name { get; set; }
}
Hello rahul I have just solved the problem you are facing it is not the perfect way but it will work in your code. try to follow it.
first I made a singleton class which store your previous selected items (lstSubSelectedItems)..like this
public class checkClass
{
static ObservableCollection<Subject> _lstSubSelectedItems = new ObservableCollection<Subject>();
static checkClass chkclss;
public static checkClass GetInstance()
{
if (chkclss == null)
{
chkclss = new checkClass();
}
return chkclss;
}
public ObservableCollection<Subject> lstSubSelectedItems
{
get
{
return _lstSubSelectedItems;
}
set
{
_lstSubSelectedItems = value;
}
}
}
i have filled lstSubSelectedItems on pagenavigationfrom method like this.. here lstsub is selectedsubjects..
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
checkClass obj = checkClass.GetInstance();
obj.lstSubSelectedItems = lstsub;
}
Here is the workaround what I have done in my constructor...
Here I removed the non selected items using removeat function of gridview.selecteditems other function are not doing this this for for (I don't know why). subject class is just like your model class . and also setting of selecteditems is not working that why I choose this way... Hope this help.
public SelectSubject()
{
this.InitializeComponent(); // not required
objselectsubjectViewmodel = new SelectSubjectViewModel(); // not required
groupedItemsViewSource.Source = objselectsubjectViewmodel.Categories; // not required the way set the itemssource of grid.
this.DataContext = this;
checkClass obj = checkClass.GetInstance();
if (obj.lstSubSelectedItems.Count > 0)
{
// List<Subject> sjsfj = new List<Subject>();
// ICollection<Subject> fg = new ICollection<Subject>();
itemGridView.SelectAll();
// int i = 0;
List<int> lstIndex = new List<int>();
foreach (Subject item1 in itemGridView.SelectedItems)
{
foreach (var item3 in obj.lstSubSelectedItems)
{
if (item3.SubjectCategory == item1.SubjectCategory && item3.SubjectName == item1.SubjectName)
{
lstIndex.Add(itemGridView.SelectedItems.IndexOf(item1));
}
}
}
int l = itemGridView.SelectedItems.Count;
for (int b = l-1; b >= 0; b--)
{
if (!lstIndex.Contains(b))
{
itemGridView.SelectedItems.RemoveAt(b);
}
}
}
}
tell me if it works for you...
You can set selectedItems property of gridView for doing this first make observableCollection and the continuously update this collection on selectionchange Event of your gridView . and when you comeback to this page set the GridViewName.SelectedItems = aboveCollection;
private ObservableCollection<Subject> lstsub = new ObservableCollection<Subject>() ;
private void itemGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
checkTemp = 1;
GridView tempObjGridView = new GridView();
tempObjGridView = sender as GridView;
lstsub = tempObjGridView.SelectedItems;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
yourGridName.SelectedItems = lstsub ;
}