How to Update ComboBox ItemsSource Without Flickering - c#

I am struggling with an update to a ComboBox that previously worked. I originally had its ItemsSource bound to a read-only ObservableCollection<char> property in the ViewModel. When the user instigates changes (which is done with mouse strokes, so dozens of times per second in some cases), the get rebuilds the collection from the Model and returns it.
When I changed to my own object in the ObservableCollection, the ComboBox started flickering during updates. I'm not sure what's going wrong. Here's the code that works, starting with the XAML:
<ComboBox ItemsSource='{Binding FromBins}' SelectedValue='{Binding SelectedFromBin, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}' />
ViewModel:
public ObservableCollection<char> FromBins
{
get
{
ObservableCollection<char> tempBins = new ObservableCollection<char>();
foreach (var item in Map.BinCounts)
{
tempBins.Add(item.Key);
}
return tempBins;
}
}
I simply raise a property change with every mouse movement and the interface works as expected (there is some other logic to ensure the SelectedItem is valid).
To make the interface more useful, I decided to add more information to the ComboBox, using my own class:
public class BinItem : IEquatable<BinItem>
{
public char Bin { get; set; }
public SolidColorBrush BinColor { get; set; }
public string BinColorToolTip { get {...} }
public BinItem( char bin )
{
Bin = bin;
BinColor = new SolidColorBrush(BinColors.GetBinColor(bin));
}
public bool Equals(BinItem other)
{
return other.Bin == Bin ? true : false;
}
}
If I swap char out for BinItem in the working code ViewModel I get flickering as the mouse is moved. Here is the updated XAML:
<ComboBox ItemsSource='{Binding FromBins}' SelectedValue='{Binding SelectedFromBin, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}'>
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" ToolTip='{Binding BinColorToolTip}'>
<Rectangle Fill='{Binding BinColor}' Width='10' Height='10' HorizontalAlignment='Center' VerticalAlignment='Center' Margin='0,0,4,0' Stroke='#FF747474' />
<TextBlock Text="{Binding Bin}" Width='16' />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have tried numerous things, including but not limited to:
-Using a List instead of the ObservableCollection but even though the Get fires every time and returns the correct collection of items, the interface does not always update (though the flickering disappears).
-Leaving all possible bins in the items source and adding a Visibility property to the BinItem class that I bound to (couldn't get it to update).
I suspect I am doing something fundamentally wrong, but no amount of searching SO or otherwise has helped thus far. Any help is appreciated.

I was able to solve this using the ideas from Clemens and Chris. Not sure if this is the most elegant solution, but it works as intended with no measurable performance hit.
Instead of replacing the collection with each refresh, I go through the logic of finding out what's changed (with each update there could be an addition AND a removal simultaneously). Code below:
private ObservableCollection<BinItem> _FromBins = new ObservableCollection<BinItem>();
public ObservableCollection<BinItem> FromBins
{
get
{
if (_FromBins.Count > 0)
{
List<char> BinsToRemove = new List<char>();
foreach (var item in _FromBins)
{
if (!Map.BinCounts.ContainsKey(item.Bin))
{
BinsToRemove.Add(item.Bin);
}
}
foreach (var item in BinsToRemove)
{
_FromBins.Remove(new BinItem(item));
}
}
foreach (var item in Map.BinCounts)
{
if (!_FromBins.Contains(new BinItem(item.Key)) && item.Value > 0) {
_FromBins.Add(new BinItem(item.Key));
}
}
return _FromBins;
}
}
Hope this can help someone else too.

Related

How can I implement a carousel of images in WPF where the selected item is always the first one?

I am creating a WPF application to act as a front end for a video games library and I'm attempting to mimic the Netflix UI. One of the features in this application is to cycle through images of games to select which game you want to play.
The desired behavior is different than the behavior when arrowing through the items in a ListBox: when you arrow through items in a ListBox, your selection moves up and down. The behavior I'm looking to implement is that as you arrow through the items, the selected item is always at the first position and the items are cycling across the selector. The term for this would be a carousel where the selected item is at index 0.
I've implemented this poorly and to give some context, here's a picture of how my interface currently looks:
My current implementation
To achieve this, I believe what I should do is extend the StackPanel class or maybe implement my own Panel. But details on custom panels are a bit complicated and hard to come by. I want to show what I've done to this point to get this working but I'm very unhappy with these implementations and I'd like to get some advice on what direction I should go for a proper implementation.
Here are some details on what I've tried.
The screenshot above is a result of a GameList class that I created which implements INotifyPropertyChanged and includes properties for 15 different games.
private GameMatch game0;
public GameMatch Game0
{
get { return game0; }
set
{
if (game0 != value)
{
game0 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game0"));
}
}
}
private GameMatch game1;
public GameMatch Game1
{
get { return game1; }
set
{
if (game1 != value)
{
game1 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game1"));
}
}
}
// identical code for games 2-10
private GameMatch game11;
public GameMatch Game11
{
get { return game11; }
set
{
if (game11 != value)
{
game11 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game11"));
}
}
}
private GameMatch game12;
public GameMatch Game12
{
get { return game12; }
set
{
if (game12 != value)
{
game12 = value;
PropertyChanged(this, new PropertyChangedEventArgs("Game12"));
}
}
}
I've laid the images out in my XAML and added enough so that they will run off the edge of the screen:
<StackPanel Name="GameImages" Orientation="Horizontal">
<Border BorderThickness="2" BorderBrush="AntiqueWhite">
<Image Name="Image_Game1" Source="{Binding CurrentGameList.Game1.FrontImage}"/>
</Border>
<Image Source="{Binding CurrentGameList.Game2.FrontImage}"/>
<!-- identical images for games 3-10 -->
<Image Source="{Binding CurrentGameList.Game11.FrontImage}" />
<Image Source="{Binding CurrentGameList.Game12.FrontImage}" />
</StackPanel>
I implemented a ListCycle class which can take any arbitrary list and a count of items that you want to cycle. In case it helps, here's the code for the ListCycle. It takes care of cycling the lists by tracking the index of items in list that should be displayed on screen in a given position.
public class ListCycle<T>
{
// list of games or whatever you want
public List<T> GenericList { get; set; }
// indexes currently available to display
// will cycle around the size of the generic list
public int[] indices;
public ListCycle(List<T> genericList, int activeCycleCount)
{
GenericList = genericList;
indices = new int[activeCycleCount];
InitializeIndices();
}
private void InitializeIndices()
{
if (GenericList != null)
{
int lastIndex = -1;
for (int i = 0; i < indices.Length; i++)
{
indices[i] = GetNextIndex(lastIndex);
lastIndex = indices[i];
}
}
}
private int GetNextIndex(int currentIndex)
{
currentIndex += 1;
if (currentIndex == GenericList.Count)
{
currentIndex = 0;
}
return currentIndex;
}
private int GetPreviousIndex(int currentIndex)
{
currentIndex -= 1;
if (currentIndex == -1)
{
currentIndex = GenericList.Count - 1;
}
return currentIndex;
}
public int GetIndexValue(int index)
{
return indices[index];
}
public T GetItem(int index)
{
return GenericList[indices[index]];
}
public void CycleForward()
{
for (int i = 0; i < indices.Length; i++)
{
if (i + 1 < indices.Length - 1)
{
indices[i] = indices[i + 1];
}
else
{
indices[i] = GetNextIndex(indices[i]);
}
}
}
public void CycleBackward()
{
for (int i = indices.Length - 1; i >= 0; i--)
{
if(i - 1 >= 0)
{
indices[i] = indices[i - 1];
}
else
{
indices[i] = GetPreviousIndex(indices[i]);
}
}
}
}
So when you press right, I cycle forward and reset the game images. When you press left, I cycle backward and reset the game images. The RefreshGames method takes care of updating all of those game properties in my game list.
private void RefreshGames()
{
Game0 = gameCycle.GetItem(0);
Game1 = gameCycle.GetItem(1);
Game2 = gameCycle.GetItem(2);
Game3 = gameCycle.GetItem(3);
Game4 = gameCycle.GetItem(4);
Game5 = gameCycle.GetItem(5);
Game6 = gameCycle.GetItem(6);
Game7 = gameCycle.GetItem(7);
Game8 = gameCycle.GetItem(8);
Game9 = gameCycle.GetItem(9);
Game10 = gameCycle.GetItem(10);
Game11 = gameCycle.GetItem(11);
Game12 = gameCycle.GetItem(12);
}
This approach works but it doesn't work well. It's not dynamic, it doesn't scale well and it doesn't perform all that well. Arrowing through images one at a time performs just fine but trying to quickly move through them, is a bit slow and feels clunky. It's not a very good user experience.
I tried a second approach, using a listbox bound the to my list of games. And to cycle the games to the left, I would remove the first item from my list of games and insert it at the end of the list. To go to the right, I would remove the item at the end of the list and insert it at index 0. This also worked but it didn't perform very well either.
So I'm looking for suggestions on a better way to implement this that would give better performance (smoother scrolling) and be more dynamic (i.e. this approach may not work well on an ultrawide monitor - 12 games may not be enough depending on the widths of the images). I'm not looking for anyone to solve it for me but point me in the right direction as I'm very new to WPF.
My feeling is that I should be extending the stack panel class and changing the way you cycle through the items or maybe creating my own panel. Can anyone confirm if this is the best approach and if so point me to some good resources to help me understand how to create a custom panel that changes the way navigation is done? I've been reading articles on creating custom panels to try to get my head around that process.
Being new to WPF, I want to make sure I'm not going down a rabbit hole or trying to reinvent a wheel that already exists. So the question is whether a custom panel is the right approach to solving this problem?
I believe what I should do is extend the StackPanel class
WPF encourages composition of existing Controls over inheritance; in your case inheriting the StackPanel looks too complicated for your purpose when you could achieve the same with your second approach:
I would remove the first item from my list of games and insert it at the end of the list
This indeed looks more like idiomatic WPF, especially if you try to follow the MVVM design pattern.
or maybe creating my own panel
This is not an easy step especially if you're new to WPF but that would be very interesting for you. That could be a way to go, especially if you internally rely on a StackPanel (composition) instead of inheriting from it.
Example implementation with an ItemsControl
I will use an ItemsControl which can display a collection of data for you (in your case, you have some GameMatch).
First define the data behind the interface, ie a collection of GameMatch. Let's give each GameMatch a name and a variable IsSelected which tells if the game is selected (ie in first position). I'm not showing the INotifyPropertyChanged implementation but it should be there for both properties.
public class GameMatch : INotifyPropertyChanged {
public string Name { get => ...; set => ...; }
public bool IsSelected { get => ...; set => ...; }
}
Your carousel interface is interested in a collection of GameMatch, so let's create an object to model this.
Our graphical interface is gonna bind to the Items property to display the collection of games.
It is also gonna bind to the two commands that are implemented such as to shift the list to the left or to the right. You can use the RelayCommand to create commands. In a nutshell, a Command is simply an action that gets executed and that you can easily refer to from your interface.
public class GameCollection {
// Moves selection to next game
public ICommand SelectNextCommand { get; }
// Moves selection to previous game
public ICommand SelectPreviousCommand { get; }
public ObservableCollection<GameMatch> Items { get; } = new ObservableCollection<GameMatch> {
new GameMatch() { Name = "Game1" },
new GameMatch() { Name = "Game2" },
new GameMatch() { Name = "Game3" },
new GameMatch() { Name = "Game4" },
new GameMatch() { Name = "Game5" },
};
public GameCollection() {
SelectNextCommand = new RelayCommand(() => ShiftLeft());
SelectPreviousCommand = new RelayCommand(() => ShiftRight());
SelectFirstItem();
}
private void SelectFirstItem() {
foreach (var item in Items) {
item.IsSelected = item == Items[0];
}
}
public void ShiftLeft() {
// Moves the first game to the end
var first = Items[0];
Items.RemoveAt(0);
Items.Add(first);
SelectFirstItem();
}
private void ShiftRight() {
// Moves the last game to the beginning
var last = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, last);
SelectFirstItem();
}
}
The key here is the ObservableCollection class which will tell the view whenever it changes (for example, everytime we move items around inside it) so the view will update to reflect this.
Then, the view (your XAML) should specify how to display the collection of games. We're gonna use an ItemsControl laying out items horizontally:
<StackPanel>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="10" Background="Beige" BorderBrush="Black" Width="150" Height="50">
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="1" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="BorderThickness" Value="5" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<TextBlock Text="{Binding Name}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Previous" Command="{Binding SelectPreviousCommand}"/>
<Button Content="Next" Command="{Binding SelectNextCommand}"/>
</StackPanel>
</StackPanel>
Notice the ItemsControl ItemsSource="{Binding Items}" which tells the ItemsControl to display all the objects in the Items property. The ItemsControl.ItemsPanel part tells to lay them out in an horizontal StackPanel. The ItemsControl.ItemTemplate part explains how each game should be displayed, and the DataTrigger within tells WPF to increase the border thickness for the selected item. Finally, the StackPanel at the bottom displays two Button which call SelectPreviousCommand and SelectLeftCommand in our GameCollection.
Finally, you should set the DataContext of the whole thing to a new GameCollection:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
DataContext = new GameCollection();
}
}
From there you can customize the UI as you'd like.
Animations and smooth scrolling
That is a whole other topic but you could for example trigger a translation animation of all your items when clicking one of the buttons.
I'll try and point you in the right direction. If you haven't already checked it out, I would try to make your application follow the MVVM pattern. In your case, the ViewModel would have an ObservableCollection of "Games". You would then bind your ItemsControl's source to that collection.
As far as getting the carousel to work, I think the path you will want to go down is creating a custom ItemsControl or ListBox. You can override the styling and create some custom behavior to get the carousel to work how you would like it to.
I can probably help out more if you have a more specific question.

Copy object on Drag and Drop

I'm having two ListBoxes, both having their ItemsSource bound to separate ObservableCollection<ICustomObject>. ICustomObject is an interface defining some base-properties for different types of CustomObject.
I want one ListBox to be the static, non changing source of possible elements from where the user can drag them to the other ListBox multiple times. Also elements in the target should be rearrangeable.
Result should be a toolbox from where the user can build a document composed of multiple CustomObject.
I'm using the GongSolutions.WPF.DragDrop library for this, commit c680fcf.
<ListBox ItemsSource="{Binding AvailableElements}" dd:DragDrop.IsDragSource="True" dd:DragDrop.DragDropCopyKeyState="ControlKey">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding SelectedElements}" dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Having this I can copy elements from source to target by holding the ControlKey.
But there are two problems with this:
Is there any way to default to copy-action, so that no key needs to be pressed?
How can I do a real copy? Currently all list-elements of the target-list are pointing to the same object with the result that changing the properties of individual elements is not possible.
I already tried it with a custom DropHandler but this makes reordering the elements impossible:
public void Drop(IDropInfo dropInfo)
{
IFormElement data = Activator.CreateInstance(dropInfo.Data.GetType()) as IFormElement;
if (data != null)
{
SelectedElements.Add(data);
}
}
Any help and hints appreciated.
"but this makes reordering the elements impossible" Why? It shouldn't, unless you're doing something wrong. To do what you want, a custom handler is the way to go, so you should detail why it wasn't working.
The collection into which you are dropping objects should implement INotifyCollectionChanged in order for the UI to update when you add new items into it. If that isn't your issue, please edit your question to add details. Assuming that isn't the problem, I'll suggest an alternative, simpler and sometimes much better alternative.
I recently added drag/drop using this library to my application. I had to create a custom drop handler as the collection being dropped on contained relationships to, not instances of, the class being dragged.
Think of it like a relational database. You have two tables-- People and Pets. Pets can be owned by multiple people, and people can have multiple pets. In a database, you would have a many-to-many table.
People -> PeoplePets <- Pets
PeoplePets describes the relationship between people and their pets, and pets to their people.
In your design, you're literally cloning a pet. Which means you and your girlfriend have two dogs now, both with the same name and both with a strong desire to roll in the neighbor cat's poop. That's weird (the cloning part, not the poop part), although I'm sure many folks would be happy with this arrangement.
Instead of your collection holding clones of pets, have your collection hold objects which define the relationship.
public ObservableCollection<PeoplePets> Pets {get;} =
new ObservableCollection<PeoplePets>();
And so, to the point of your question, in your custom drop handler, when someone drops a pet on your listbox, simply create a new instance of PeoplePets, drop the pet in it, and add the relationship object to the collection. You don't have to worry about cloning anything, and you won't be adding new instances of the same thing (this can be extremely helpful, depending on what you're doing with the data down the road--detecting and merging dupes is a PITA).
Followed Wills answer and created my own drop handler based on the default drop handler. This way I could overwrite the default behavior to be always copy but not within the same list.
Looking at the default code I also found that its trying to clone the objects on copy, if they implement ICloneable. So I made them cloneable returning a new instance of them self.
Here are the relevant parts of the code (DropHandler-code mostly based on original DefaultDropHandler.cs):
View.xaml:
<ListBox ItemsSource="{Binding AvailableElements}" dd:DragDrop.IsDragSource="True">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding SelectedElements}" dd:DragDrop.IsDropTarget="True" dd:DragDrop.IsDragSource="True" dd:DragDrop.DropHandler="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel.cs (must implement IDropTarget)
public void DragOver(IDropInfo dropInfo)
{
DragDrop.DefaultDropHandler.DragOver(dropInfo);
}
public void Drop(IDropInfo dropInfo)
{
if (dropInfo == null || dropInfo.DragInfo == null)
{
return;
}
var insertIndex = dropInfo.InsertIndex != dropInfo.UnfilteredInsertIndex ? dropInfo.UnfilteredInsertIndex : dropInfo.InsertIndex;
var destinationList = dropInfo.TargetCollection.TryGetList();
var data = ExtractData(dropInfo.Data);
// default to copy but not if source equals target
var copyData = (!Equals(dropInfo.DragInfo.SourceCollection, dropInfo.TargetCollection))
&& !(dropInfo.DragInfo.SourceItem is HeaderedContentControl)
&& !(dropInfo.DragInfo.SourceItem is HeaderedItemsControl)
&& !(dropInfo.DragInfo.SourceItem is ListBoxItem);
if (!copyData)
{
var sourceList = dropInfo.DragInfo.SourceCollection.TryGetList();
foreach (var o in data)
{
var index = sourceList.IndexOf(o);
if (index != -1)
{
sourceList.RemoveAt(index);
if (Equals(sourceList, destinationList) && index < insertIndex)
{
--insertIndex;
}
}
}
}
var tabControl = dropInfo.VisualTarget as TabControl;
// clone data but not if source equals target
var cloneData = !Equals(dropInfo.DragInfo.SourceCollection, dropInfo.TargetCollection);
foreach (var o in data)
{
var obj2Insert = o;
if (cloneData)
{
var cloneable = o as ICloneable;
if (cloneable != null)
{
obj2Insert = cloneable.Clone();
}
}
destinationList.Insert(insertIndex++, obj2Insert);
if (tabControl != null)
{
var container = tabControl.ItemContainerGenerator.ContainerFromItem(obj2Insert) as TabItem;
if (container != null)
{
container.ApplyTemplate();
}
tabControl.SetSelectedItem(obj2Insert);
}
}
}
public static IEnumerable ExtractData(object data)
{
if (data is IEnumerable && !(data is string))
{
return (IEnumerable)data;
}
else
{
return Enumerable.Repeat(data, 1);
}
}
#Will: thanks for your answer, it pointed me in the right direction. To help others I will answer my own question, but I upvoted your answer.
I can answer your second question. To do a real copy you can use the following class or just use the cloning part in your code:
public class GenericCloner<T> where T : class
{
public T Clone(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}

LongListSelector and DataTemplateSelector

I'm using the LongListSelector to realize List or Grid display for my items. For this, I created a DataTemplateSelector and I change the LayoutMode property at runtime. This is working but there seems to be an issue with the DataTemplateSelector. If I initially launch the page, the DataTemplateSelector is called three times for my three items. When I navigate to another page (settings page to change the LayoutMode) and then back, the DataTemplateSelector is just called two items but there are still three items.
DataTemplateSelector:
public abstract class DataTemplateSelector : ContentControl
{
public virtual DataTemplate SelectTemplate(object item, DependencyObject container)
{
return null;
}
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ContentTemplate = SelectTemplate(newContent, this);
}
}
ItemViewModeTemplateSelector:
public class ItemViewModeTemplateSelector: DataTemplateSelector
{
public DataTemplate ListViewModeTemplate
{
get;
set;
}
public DataTemplate GridViewModeTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ViewMode viewMode = ViewMode.Grid;
// Get ViewMode from IsolatedStorageSettings...
switch (viewMode)
{
case ViewMode.Grid:
return GridViewModeTemplate;
case ViewMode.List:
return ListViewModeTemplate;
}
return base.SelectTemplate(item, container);
}
}
MainPage.xaml:
<phone:LongListSelector x:Name="ItemLongListSelector" ItemsSource="{Binding Items}" LayoutMode="Grid" GridCellSize="222,222">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<common:ItemViewModeTemplateSelector Content="{Binding}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch">
<common:ItemViewModeTemplateSelector.GridViewModeTemplate>
<DataTemplate>
<StackPanel Margin="12,12,0,0" Background="{Binding Color, Converter={StaticResource ColorToBrushConverter}}">
<!-- Content -->
</StackPanel>
</DataTemplate>
</common:ItemViewModeTemplateSelector.GridViewModeTemplate>
<common:ItemViewModeTemplateSelector.ListViewModeTemplate>
<DataTemplate>
<StackPanel>
<!-- Content -->
</StackPanel>
</DataTemplate>
</common:ItemViewModeTemplateSelector.ListViewModeTemplate>
</common:ItemViewModeTemplateSelector>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
This is the display when I initially launch the page:
Then I navigate to another page and then back:
EDIT: I prepared a sample project for this issue. It should run without problems.
Project: http://sdrv.ms/1cAbVxE
I haven't got the solution but maybe a clue for someone who will solve the problem.
I think the problem is with LongListSelector.UpdateLayout() method - when it's fired for the first time there are no items to which LLS was bound - OnChangeMethod is called that many times as the Itemsource.Count. But when we leave the page and go back - LLS is Updated and method is called ommiting the middle element.
It means it works for even number of items - OnChangeMethod is called correct number of times, But for odd number of items - it's called numer of items - 1.
The second thing is why it's called at all - when there are no changes.
I also add a code to work on which (very simple).
I've done something similar with my app, but allowed the user to choose the LayoutMode of LLS using an Appbar button. I basically change the LongListSelector.LayoutMode and then it's ItemTemplate in code and the LLS automatically refreshes itself. I'm not sure if this will help, but here's my code.
private void layoutModeButton_Click(object sender, EventArgs e)
{
ApplicationBarIconButton layoutModeButton = (ApplicationBarIconButton)ApplicationBar.Buttons[0];
if (MainLongListSelector.LayoutMode == LongListSelectorLayoutMode.Grid)
{
MainLongListSelector.LayoutMode = LongListSelectorLayoutMode.List;
MainLongListSelector.ItemTemplate = this.Resources["ListListLayout"] as DataTemplate;
layoutModeButton.IconUri = _gridButtonUri;
layoutModeButton.Text = "grid";
}
else
{
MainLongListSelector.LayoutMode = LongListSelectorLayoutMode.Grid;
MainLongListSelector.ItemTemplate = this.Resources["GridListLayout"] as DataTemplate;
layoutModeButton.IconUri = _listButtonUri;
layoutModeButton.Text = "list";
}
}
You might have figured out the answer already, but just to add to the conversation: this gives me really good performance for a fairly large amount of data. Maybe you can do something similar when navigating back to the page after changing the layout in settings?
Here is one walk around. (Maybe the problem will be corrected with WP 8.1 Update, along with others I've spotted working with LLS. I know - this idea is ugly, hard and so on, but maybe it will be enough for your purpose:
Becouse the problem concerns 'reloading' LLS, I've forced it to initialize it every time I navigate to the page (in fact I need to initialize the whole Page - it won't work only with LLS). I've moved InitializeComponent() and buttons events and so on to OnNavigatedTo():
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this._contentLoaded = false;
InitializeComponent();
first.Click += first_Click;
second.Click += second_Click;
ItemLongListSelector.ItemsSource = Items;
}
At least OnContentChanged() is fired that many Times its needed. Code you can find here.

Failing to generate dynamic Checkboxes

I am working on a WPF where I need to dynamically generate Checkboxes 16 times.
XAML:
<Checkboxes Height="14" Command="{Binding CheckboxesGen}" Margin="0" Name="checkBox1" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center" />
Using above way, It will be inefficient if I write down this Checkboxes 16 times and have individual Button Click Command for them. I would ideally want to generate them 16 times and have one common method in my viewmodel class as follows:
private ICommand mCheckboxesGen;
public ICommand CheckboxesGen
{
get
{
if (mCheckboxesGen== null)
mCheckboxesGen= new DelegateCommand(new Action(mCheckboxesGenExecuted), new Func<bool>(mCheckboxesGenCanExecute));
return mCheckboxesGen;
}
set
{
mCheckboxesGen= value;
}
}
public bool mCheckboxesGenCanExecute()
{
return true;
}
public void mCheckboxesGenExecuted(some INDEX parameter which gives me selected Checkboxes )
{
// Have a common method here which performs operation on each Checkboxes click based on INDEX which determines which Checkboxes I have selected
}
I had faced the same situation in my C++ app. I had done it in my C++ app as follows:
for(int j = 0; j < 16; j ++)
{
m_buttonActiveChannels[j] = new ToggleButton();
addAndMakeVisible(m_buttonActiveChannels[j]);
m_buttonActiveChannels[j]->addButtonListener(this);
}
//Checking which Checkboxes is clicked
unsigned bit = 0x8000;
for(int i = 15; i >= 0; i--)
{
if(0 != (value & bit)) //Value has some hardcoded data
{
m_buttonActiveChannels[i]->setToggleState(true);
}
else
{
m_buttonActiveChannels[i]->setToggleState(false);
}
bit >>= 1;
}
Hence using this generates it 16 times and has one method which performs operation based on index i.
Using a similar approach or any other approach, How can I achieve it in my wpf app? :)
Please help :)
How about something like this?
<ItemsControl ItemsSource="{Binding CollectionOfObjectsThatRepresentYourCheckBox}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Checkbox Content="{Binding DisplayText }" Checked="{Binding Checked}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
you would need to populate your collection on objects on load or when a command was executed, then you could react to items being checked in the model that you create for it..
public class CheckBoxClass
{
public int Index {get; set;}
public string DisplayText {get; set}
private bool _checked;
public bool Checked
{
get { return _checked;}
set {
_checked = value
doSomethingWhenChecked();
}
}
ObservableCollection<CheckBoxClass> CollectionOfObjectsThatRepresentYourCheckBox = SomeMethodThatPopulatesIt();
this is a much cleaner way to do this and instead of generating the controls you will be just binding to a list of your objects that will be represented by a check box.
Define a viewmodel for the checkboxes, this class will have an Index property and the command implementation based on it. Add an ObservableCollection of checkboxes viewmodels to your current viewmodel. In the view add an ItemsControl bound to this collection with a proper ItemTemplate. You can now add as many checkboxes as you want dynamically in the viewmodel.

UI slow at updating ObservableCollection<T> in TreeView control

Scenario
I have a TreeView that is bound to ObservableCollection<T>. The collection gets modified every time the end-user modifies their filters. When users modify their filters a call to the database is made (takes 1-2ms tops) and the data returned gets parsed to create a hierarchy. I also have some XAML that ensures each TreeViewItem is expanded, which appears to be part of the problem. Keep in mind that I'm only modifying ~200 objects with a max node depth of 3. I would expect this to instant.
Problem
The problem is that whenever filters get modified and the TreeView hierarchy gets changed the UI hangs for ~1 second.
Here is the XAML responsible for create the TreeView hierarchy.
<TreeView VerticalAlignment="Top" ItemsSource="{Binding Hierarchy}" Width="240"
ScrollViewer.VerticalScrollBarVisibility="Auto" Grid.Row="1">
<TreeView.ItemTemplate>
<!-- Hierarchy template -->
<HierarchicalDataTemplate ItemsSource="{Binding Stations}">
<TextBlock Text="{Binding}" />
<!-- Station template -->
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Locates}">
<TextBlock Text="{Binding Name}" />
<!-- Locate template -->
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TicketNo}" />
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
And here is the code for updating the list.
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set { _hierarchy = value; }
}
public void UpdateLocates(IList<string> filterIds)
{
_hierarchy.Clear();
// Returns 200 records max
var locates = _locateRepository.GetLocatesWithFilters(filterIds);
var dates = locates.Select(x => x.DueDate);
foreach (var date in dates)
{
var vm = new HierarchyViewModel
{
DueDate = date
};
var groups = locates.Where(x => x.DueDate.Date.Equals(date.Date)).GroupBy(x => x.SentTo);
// Logic ommited for brevity
_hierarchy.Add(vm);
}
}
I also have <Setter Property="IsExpanded" Value="True" /> as a style. I have tried using a BindingList<T> and disabling notifications, but that didn't help.
Any ideas as to why my UI hangs whenever changes are made to the ObservableCollection<T>?
Partial Solution
With what H.B. said and implementing a BackgroundWorker the update is much more fluid.
The problem is probably the foreach loop. Every time you add an object the CollectionChanged event is fired and the tree is rebuilt.
You do not want to use an ObservableCollection if all you do is clear the whole list and replace it with a new one, use a List and fire a PropertyChanged event once the data is fully loaded.
i.e. just bind to a property like this (requires implementation of INotifyPropertyChanged):
private IEnumerable<HierarchyViewModel> _hierarchy = null;
public IEnumerable<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set
{
if (_hierarchy != value)
{
_hierarchy = value;
NotifyPropertyChanged("Hierarchy");
}
}
}
If you set this property bindings will be notified. Here i use the IEnumerable interface so no-one tries to just add items to it (which would not be noticed by the binding). But this is just one suggestion which may or may not work for your specific scenario.
(Also see sixlettervariable's good comment)
Just a side note, this code:
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
set { _hierarchy = value; }
}
is bad, you could overwrite the list and the binding would break because there is no PropertyChanged event being fired in the setter.
If you use an ObservableCollection it normally is used like this:
private readonly ObservableCollection<HierarchyViewModel> _hierarchy =
new ObservableCollection<HierarchyViewModel>();
public ObservableCollection<HierarchyViewModel> Hierarchy
{
get { return _hierarchy; }
}
The easiest thing to do is detach the item you are bound to, make all the changes you need to the list, then reattach it.
For example, set the treeviews ItemsSource to NULL/NOTHING, run through your for each, then set the ItemsSource back to _hierarchy. Your adds will be instant.

Categories