Best way to set BIG IEnumerable as ListBox.ItemSource - c#

I'm trying to use a Microsoft.Windows.APICodePack.Shell.ShellContainer as ItemsSource for a ListBox, showing each child's (ShellObject) Thumbnail and Name via ListBox.ItemTemplate.
The problem arises when ShellContainer refers to a VERY BIG folder (say more than one thousand files): if I simply declare
ShellContainer source=ShellObject.FromParsingName(#"C:\MyFolder") as ShellContainer:
listBox1.ItemsSource=source.GetEnumerator();
it freezes the UI for two or three minutes, then displays ShellContainer's content all at once.
The best workaround I've found is to create an async filler class like this
class AsyncSourceFiller
{
private ObservableCollection<ShellObject> source;
private ShellContainer path;
private Control parent;
private ShellObject item;
public AsyncSourceFiller(ObservableCollection<ShellObject> source, ShellContainer path, Control parent)
{
this.source = source;
this.path = path;
this.parent = parent;
}
public void Fill()
{
foreach (ShellObject obj in path)
{
item = obj;
parent.Dispatcher.Invoke(new Action(Add));
Thread.Sleep(4);
}
}
private void Add()
{
source.Add(item);
}
}
and then call it via
ObservableCollection<ShellObject> source = new ObservableCollection<ShellObject>();
listBox1.ItemsSource = source;
ShellContainer path = ShellObject.FromParsingName(#"C:\MyFolder"):
AsyncSourceFiller filler = new AsyncSourceFiller(source, path, this);
Thread th = new Thread(filler.Fill);
th.IsBackground = true;
th.Start();
This takes more time than the previous way, but doesn't freeze the UI and begins to show some content immediately.
Is there any better way to obtain a similar behavior, possibly shortening total operation time?

load all data in background thread and when finished update itemssource of your listbox. and do not forget to set Virtualization to true in your listbox.

The time consuming operation is to enumerate your ShellContainer and create thousands of ShellObject. ListBox is not the issue.
When you set an IEnumerable as source to an ItemControl, I think that it creates and internal list out of the enumerator the first time displayed, and that is why it freezes for two minutes before showing anything.
You do not have many options here:
Create yourself a List<ShellObject> and set it as source of our ListBox. It isn't faster but at least you can display a "Loading, please wait" message to your users.
Load the list in another thread (as you do) and display items as they load. It's a bit weird as the list "grows" over time.
Find a way to wrap your ShellContainer in a class that implements IList. For this, you need to be able to get an item at a given index in the ShellContainer class (I don't know "Windows API code pack"). If you use this as source of you ListBox and virtualization is enabled, only the displayed ShellObjects will be loaded, and it will be fast and smooth.

Related

C# WPF Affecting main thread from UserControl class

I am trying to make a simple image viewer that loads images at start, creates Items with pulled SQL data and then creates Dictionary with all the Item parameters.
Then another method builds what's needed and fills StackPanel with UserControls (ITEM) that act as "links" to images.
Trying to add another image, from UserControl (NEW_ITEM) nested in View that is loaded to ContentControl in MainWindow.
public partial class fileUpload : UserControl
{
public fileUpload()
{
InitializeComponent();
}
string PathSource = #"SOURCE";
string PathToCopy = #"DESTINATION";
private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
PathSource = FU_source.Text;
if (!FU_fileName.Text.Contains("."))
{
MainWindow V = new MainWindow();
V.LoadCollection(); //This seems to work, but the content on MainWindow stays visible.
////Copy Inserted Item
File.Copy(PathSource,PathToCopy + FU_fileName.Text + ".png", true);
////Remove Item Preview From List
((ItemsControl)this.Parent).Items.Remove(this);
}
else
{
MessageBox.Show("ERR TEST", "ERR TEST HEADER");
}
}
}
Running LoadCollection() method does work OK if it's called from the MainWindow but does not work if called from fileUpload Class. (Program seems to do what's needed, but the user control won't disappear.)
Basically, it should refresh the whole process and refresh/refill StackPanel with new items.
I have encountered this problem many times and I think that I'm missing a key part in this.
TL;DR: I am trying to refresh MainWindows StackPanel with new Items, but the controls won't disappear if method taking care of this is called from UC Class.

Wpf application and Threads

I have problem with my GUI and Threads.
The GUI contains DataGrid. Every X time the program do some query and getting a list of items that I want to fill into the DataGrid.
So far so good:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
}));
FindSelectionObject(); // <-- see next explanation.
}
When the user click on one of the objects in the datagrid, the line color changed (it works fine), but when the program reload the table,The painted line disappears (Becuse I clear and add new objects).
To deal with it, I created the function FindSelectionObject():
private void FindSelectionObject()
{
this.Dispatcher.Invoke((Action)(() =>
{
this.SelectedIndex = TaskListTable.Items.IndexOf((myObject)lastSelectionObject); //find index of the new object that equels to the last selection object.
var row = TaskListTable.ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as DataGridRow; //get the row with the index
row.Background = Brushes.LightGoldenrodYellow; //repaint
}));
}
The problem: Everything works fine, but sometimes when the program reloads, the line flashes per second and then highlighted back, and sometimes it's not painting it at all (untill the next reload).
I can't understand why this is happening. I think maybe the FindSelectionObject() begins to run before the loadTaskList() ends to invoke all and add the new objects into the datagrid.
But if so - Why? And how can I fix it?
In the bottom line, I want that after every reload the line re-paint immediately..
Thanks for any advice!
A few things to think about:
You should keep in mind that the DataGrid uses virtualization, which means that each item in your items source does not get its very own UI element. The UI elements are created to fill the visible area, and then re-used depending on which data-source item is currently bound to each one (this changes when you scroll for instance or change the items source). This may cause you problems in the future if you use your current approach, so keep this in mind.
The other thing is that the DataGrid may require more "cycles" of the layout process in order to update its UI. You may simply be calling FindSelectionObject prematurely. You have queued FindSelectionObject right after the invocation in loadTaskList. If the DataGrid needs to perform some actions which are queued on the dispatcher after the items source has changed, these will execute after the invocation in FindSelectionObject.
Try this instead:
private void loadTaskList() //Call every X time
{
List<myObject> myList = myquery();
this.Dispatcher.Invoke((Action)(() =>
{
TaskListTable.Items.Clear(); //Clear the DataGrid
foreach (myObject O in myList) //Add the items from the new query.
{
TaskListTable.Items.Add(O);
}
// The items of the grid have changed, NOW we QUEUE the FindSelectionObject
// operation on the dispatcher.
FindSelectionObject(); // <-- (( MOVE IT HERE )) !!
}));
}
EDIT: OK, so if this fails then maybe this will cover the case in which the above solution fails: subscribe to the LoadingRow event of DataGrid and set the appropriate background color if the row is the selected one. So in the cases when new rows are created this event will be called (due to virtualization it is not called per item in items source, but per actual row UI element). In the event args you will have access to the created DataGridRow instance.
I think this issue could be a visual thread synchronization. For this you can create and use a method similar like this:
public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
{
var currentSyncContext = SynchronizationContext.Current;
var backgroundWorker = new BackgroundWorker();
backgroundWorker.DoWork += (_, __) =>
{
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
currentSyncContext.Send((t) =>
{
IsBusy = true;
BusyText = string.IsNullOrEmpty(text) ? "Espere por favor..." : text;
if (beforeVisualAction != null)
beforeVisualAction();
}, null);
action();
currentSyncContext.Send((t) =>
{
IsBusy = false;
BusyText = "";
if (afterVisualAction != null)
afterVisualAction();
}, null);
};
backgroundWorker.RunWorkerAsync();
}
IsBusy and BusyText are particular properties, that you can remove. The action variable will be the action to do in background (load your items for instance). beforeVisualAction and afterVisualAction are the visual actions you may want to do before and after the background action. Here are any visual update, for instance select your item, change color, set a view model variable that raise a binding update,... (any action that update the view).
Hope this method helps.
Are you maintaining the reference to lastSelectionObject somewhere? You say you're adding new objects, if they are truly new then the reference will be different and the reference comparison happening in IndexOf will not find it.

Two-way bind a "virtual" list of strings to a column

I have a list of Strings.
Well, conceptually. They are stored somewhere else, but I want provide an object which acts like a list (and provides any necessary events on top of that), with properties that I could bind to.
I want to establish a two-way binding over this data, to display it as a modifiable column in a DataGrid. I have the following problems with that:
I can't make a two-way binding because the binding needs a path (i.e. I can't have it look like {Binding} or {Binding Path=.} in the column, must be {Binding Path=someField"} to be made modifiable if I got this right, which sounds reasonable).
I don't exactly know how the proxy collection object should look like, in terms of interfaces (would IEnumerable + INotifyCollectionChanged sufficient?)
Is there any solution which doesn't involve creating one proxy object per every String in the collection? Could you suggest an efficient design?
To keep the discussion on the rails, let's assume I want to bind to something like this:
class Source {
public String getRow(int n);
public void setRow(int n, String s);
public int getCount();
public void addRow(int position, String s);
public void removeRow(int position);
}
That's not exactly my case, but when I know how to bind to this, I think I'll be able to handle any situation like this.
I'm OK with having to provide an adapter object on top of that Source, with any necessary interfaces and events, but I don't want to have one adapter object per row of data.
While making an adapter for the Source is relatively clear, then, unfortunatelly, the core of the second problem ('not wrapping every string in a miniobject') is a clash built into the .Net and WPF..
The first thing is that the WPF does provide you with many ways of registering 'on data modified' callbacks, but provides no way of registering callbacks that would provide a value. I mean, the "set" phase is only extendable, not interceptable, and the "get" - nothing at all. WPF will simply keep and return whatever data it has once cached.
The second thing is that in .Net the string is ... immutable.
Now, if ever you provide a string directly as a pathless binding or as a datacontext to any control, you are screwed in a dead end. The problem is, that WPF actually passes only the actual value of the binding, without the information of "where it came from". The underlying control will be simply given the string instance, and will have no sane way of modifying it as the string cannot change itself. You will not be even notified about such attempt, just like with read-only properties. What's more - if you ever manage to intercept such a modification attempt, and if you produce a proper new string, the WPF will never ask you again for the new value. To update the UI, you'd have to mannually, literally, force the WPF to re-ask you by for example changing the original binding so it points elsewhere (to the new value) or set the datacontext (to the new instance). It is doable with some VisualTree scanning, as every 'changed' callback gives you the DependencyObjects (Controls!), so yo ucan scan upwards/downwards and tamper with their properties.. Remember that option - I'll refer to this in a minute.
So, everything boils down to the fact that to get a normal 2-way binding you do not have to have a Path, you "just" have to have a mutable underlying data object. If you have immutable one - then you have to use a binding to a mutable property that holds the immutable value..
Having said that, you simply have to wrap the strings some how if you want to modify them.
The other question is, how to do that. There's a plenty of ways to do it. Of course, you can simply wrap them like Joe and Davio suggested (note to Joe: INotify would be needed there also), or you can try to do some XAML tricks with attached properties and/or behaviours and/or converters to do that for you. This is completely doable, see for example my other post - I've shown there how to "inject a virtual property" that pulled the data completely from elsewhere (one binding+converter performed the wrapping on the fly, second binding extracted the values from the attached-wrapper). This way you could create a "Contents" property on the string, and that property could simply return the string itself, and it'd be completely 2-way bindable with no exceptions.
But.. it would NOT work 2-way-ish.
Somewhere at the root of your binding/behaviour/conveter chain, there will be an immutable string. Once your smart autowrapping binding chain fires with 'on modified' callback you will be notified with pair of old/new values. You will be able to remap the values to new and old strings. If you implemented everything perfectly, the WPF will simply use the new value. If you tripped somewhere, then you will have to push the new value artificially back to the UI (see the options I'd asked you to remember). So, it's ok. No wrapper, old value was visible, it was changeable, you've got new value, the UI displays new value. How about storage?
Somewhere in the meantime you've been given a old/new value pair. If you analyze them, you'll get old/new strings. But how do you update the old immutable string? Can't do. Even if autowrapping worked, even if UI worked, even if editing seemed to work, you are now standing with the real task: you onmodified callback was invoked and you have to actually update that immutable string piece.
First, you need your Source. Is it static? Phew. What a luck! So surely it is instanced. In the on-modified callback we got only a old+new string.. how to get the Source instance? Options:
scan the VisualTree and search for it in the datacontexts and use whatever was found..
add some more attached properties and binding to bind a virtual "Source" property to every string and read that property from the new value
Well doable, but smells, but no other options.
Wait, there's more: not only the old/new value and an instance of Source are needed! You also need the ROW INDEX. D'oh! how to get that from the bound data? Again, options:
scan the VisualTree and search for it (blaargh)...
add some more attached properties and bindings to bind a virtual "RowIndex" property to every (blaaergh)...
At this point of time, while I see that all of this seems implementable and actually might be working properly, I really think that wrapping each string in a small
public class LocalItem // + INotifyPropertyChanged
{
public int Index { get; }
public Source Source { get; }
public string Content
{
get { Source...}
set { Source... }
}
}
will simply be more readable, elegant and .. SHORTER to implement. And less error-prone, as more details will be explicit instead of some WPF's binding+attached magic..
I find your approach a little weird.
DataGrids are usually used to display Rows. Rows consist of data that belongs together.
You could for instance easily map a row to a certain class. This means that the columns in your datagrid represent properties in your class.
What you're trying to do is the opposite, you're trying to get a relation between the column values instead of the row values.
Wouldn't it be easier to have a collection of your class which you can then bound the column to?
For instance
class MyClass : INotifyPropertyChanged
{
// Remember to actually implement INotifyPropertyChanged
string Column;
}
If you would have an ObservableCollection of MyClass you could bind the DataGrid to this collection. Whenever the property which I called "Column" changes, you could update your special list.
You can do this by hooking up some events. With the implementation of INotifyPropertyChanged, your columns will be updated if you update the "Column"-value directly.
I have this bit of code I use to bind a list of custom object to a DataContextMenu. You can alter it to use a list of strings and bind it to what you need
class SampleCode
{
class Team
{
private string _TeamName = "";
private int _TeamProperty1 = 0;
ObservableCollection<Territory> _Territories = new ObservableCollection<Territory>();
public Team(string tName)
{
this.TeamName = tName;
}
public ObservableCollection<Territory> Territories
{
get { return _Territories; }
set { _Territories = value; }
}
public string TeamName
{
get { return _TeamName; }
set { _TeamName = value; }
}
public int TeamProperty1
{
get { return _TeamProperty1; }
set { _TeamProperty1 = value; }
}
}
class Territory
{
private string _TerritoryName = "";
Team _AssociatedTeam = null;
public Territory(string tName, Team team)
{
this.TerritoryName = tName;
this.AssociatedTeam = team;
}
public Team AssociatedTeam
{
get { return _AssociatedTeam; }
set { _AssociatedTeam = value; }
}
public string TerritoryName
{
get { return _TerritoryName; }
set { _TerritoryName = value; }
}
public void Method1()
{
//Do Some Work
}
}
class MyApplication
{
ObservableCollection<Team> _Teams = new ObservableCollection<Team>();
ContextMenu _TeritorySwitcher = new ContextMenu();
public MyApplication()
{
}
public void AddTeam()
{
_Teams.Add(new Team("1"));
_Teams.Add(new Team("2"));
_Teams.Add(new Team("3"));
_Teams.Add(new Team("4"));
foreach (Team t in _Teams)
{
t.Territories.Add(new Territory("1", t));
t.Territories.Add(new Territory("2", t));
t.Territories.Add(new Territory("3", t));
}
SetContextMenu();
}
private void SetContextMenu()
{
HierarchicalDataTemplate _hdtTerritories = new HierarchicalDataTemplate();
_hdtTerritories.DataType = typeof(Territory);
HierarchicalDataTemplate _hdtTeams = new HierarchicalDataTemplate();
_hdtTeams.DataType = typeof(Team);
FrameworkElementFactory _TeamFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TeamFactory.Name = "txtTeamInfo";
_TeamFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TeamProperty1"));
FrameworkElementFactory _TerritoryFactory = new FrameworkElementFactory(typeof(TreeViewItem));
_TerritoryFactory.Name = "txtTerritoryInfo";
_TerritoryFactory.SetBinding(TreeViewItem.HeaderProperty, new Binding("TerritoryProperty1"));
_hdtTeams.ItemsSource = new Binding("Territories");
_hdtTeams.VisualTree = _TeamFactory;
_hdtTerritories.VisualTree = _TerritoryFactory;
_hdtTeams.ItemTemplate = _hdtTerritories;
_TeritorySwitcher.ItemTemplate = _hdtTeams;
_TeritorySwitcher.ItemsSource = this._Teams;
}
}
}
Lazy solution
Derive from ObservableCollection<string> and let that collection be populated from the Source. In the derived class, register to collection change events and update the source accordingly. Bind the DataGrid column to the observable collection.
This should be pretty straightforward to write, but has a big drawback of duplicating all data in the collection.
More efficient solution
Create an adapter (as you suggested) and implement IList<string> and INotifyCollectionChanged. Let the list operations fall through directly to the source. Bind the DataGrid column to the adapter.
This approach would require some tedious boilerplate, but it's a thin layer between the WPF control and your Source.
This really depends on how you're implementing the UI. Bea Stollnitz did an excellent post of virtualizing the ItemsSource for the WPF DataGrid at http://bea.stollnitz.com/blog/?p=344 . With work I used this to edit as well as display data.
The easiest way is by placing the string in a wrapper class.
public class Wrapper
{
public string Content{get;set;}
}
Then you use the string via the wrapper class. This was the list items remain the same but the content changes.
The problem is when you do this without that then an old string is being deleted and a new one is created and the collection is confused.
Start with an ObservableCollection<string>. Then set the bindable control's ItemsSource to the ObservableCollection.

Adding Items to ListView too slow in C#

I want to add item to listview control. This is a bit of code :
this.lView.ListViewItemSorter = null;
ListViewItem[] lvitems = new ListViewItem[ListMyObjects.Count];
int index = 0;
foreach (MyObject object in ListMyObjects)
{
ListViewItem item = new ListViewItem();
item.Text = object.Name;
lvitems[index++] = item;
}
this.lView.BeginUpdate();
this.lView.Items.AddRange(lvitems); // Slow in here with debugger
this.lView.EndUpdate();
I'm only add about 1000 item but it's very slowly. It's spend about 15secs to finish.
why does anyone know the reason ? Thank in advance.
Edit :
I have customized listview before.
public partial class MyListView: ListView
{
public MyListView()
{
InitializeComponent();
this.View = View.Details;
this.FullRowSelect = true;
this.DoubleBuffered = true;
}
private bool mCreating;
private bool mReadOnly;
protected override void OnHandleCreated(EventArgs e)
{
mCreating = true;
base.OnHandleCreated(e);
mCreating = false;
}
public bool ReadOnly
{
get { return mReadOnly; }
set { mReadOnly = value; }
}
protected override void OnItemCheck(ItemCheckEventArgs e)
{
if (!mCreating && mReadOnly) e.NewValue = e.CurrentValue;
base.OnItemCheck(e);
}
}
I do it because i don't want to hang when i use multiple threading. I don't know what does this influenced to it ?
You could make it much faster by enabling virtual mode.
However, that will take some work.
The preferred way of adding multiple items is to use the AddRange() method. However if you must add the items one by one you can use the BeginUpdate() and EndUpdate() methods around your loop. Following is from the MSDN
The preferred way to add multiple items to a ListView is to use the AddRange method of the ListView.ListViewItemCollection (accessed through the Items property of the ListView). This enables you to add an array of items to the list in a single operation. However, if you want to add items one at a time using the Add method of the ListView.ListViewItemCollection class, you can use the BeginUpdate method to prevent the control from repainting the ListView every time that an item is added.
Appologies for a more architectural solution, but if your domain objects are large this might cause the bottleneck (reading the comments it sounds like they may be slowing it down). Before you get to the presentation layer you could flatten them into some (very simple) domain transfer objects (DTOs): literally just a bag of getters-and-setters.
A tool like AutoMapper could potentially take a lot of the donkey work out there
That way your domain objects stay in the business logic domain (where they belong) and your presentation layer just gets the data in needs from the DTO.
Sorry for the non-code-based suggestion :) good luck!

Show only a part of an ItemsControl's source at first

I have an ItemsControl displaying a collection of files. Those files are sorted by most recent modification, and there's a lot of them.
So, I want to initially only show a small part (say, only 20 or so) of them, and display a button labelled "Show More" that would reveal everything when clicked.
I already have a solution, but it involves using a good old LINQ Take on my view model's source property. I was wondering if there was a cleaner way.
Thanks.
Why not have the object that you assign to the ItemsSource handle this logic - on first assignment, it would report a limited subset of the items. When Show More is clicked, the object is updated to show more (or all entries) and then notifies the framework that the property has changed (e.g. using the IPropertyNotifyChanged).
public class MyItemSource
{
private List<string> source = { ... };
public MyItemSource()
{
this.ShowThisMany = 20;
}
public int ShowThisMany
{
get;
set; // this should call\use the INotifyPropertyChanged interface
}
public IEnumerable<string> this[]
{
return this.source.Take(this.ShowThisMany);
}
}
...
MyItemsSource myItemsSource = new MyItemsSource();
ItemsControl.Source = myItemsSource;
...
void OnShowMoreClicked(...)
{
myItemsSource.ShowThisMany = 50;
}
In order to do this, you need to create some sort of 'view' on your data. There is nothing within the WPF framwork that will give you this functionality for free. In my opinion, a simple bit of Linq, Take(), is a clean and simple solution.

Categories

Resources