Listbox shows elements wrongly after scrolling - c#

In my windows phone 8.1 application I have a listbox when initially loading everything I want to show in the listbox everything is fine. However after scrolling some of the elements will be shown incorrectly. This seems completely random.
The forementioned listbox looks like this in xaml:
<ListBox Name="MainPage_List" Grid.Column="0" Background="#EDEDED" >
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Components:MyUserControl />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see its datatemplate is linked to a usercontrol. In this UserControl I have a DataContextChanged event. Which looks like this:
private void DataContextChanged(object sender, object e)
{
if (mySource == null)
{
mySource = DataContext as Message;
}
if (mySource != null)
{
if (mySource.Source_Type == SourceTypes.Type1)
{
MyGrid.Visibility = Visibility.Collapsed;
MyOtherGrid.Visibility = Visibility.Visible;
}
else if (mySource.Source_Type == SourceTypes.Type2)
{
MyOtherGrid.Visibility = Visibility.Collapsed;
MyGrid.Visibility = Visibility.Visible;
}
}
}
I check multiple types and variables here and depending on that I set other things visible or load other images. This works fine. However when scrolling through the list sometimes some of the elements will be shown differently than they should. Even when I make sure the code that decides which elements will be shown is not used again.
The source of the list is a custom class enheriting from ObservableCollection using a custom class that enherits from INotifyPropertyChanged.
Does anyone know what I am doing wrong here? Or why this happens and how to get around this?

After some discussion with WereWolfBoy we found the problem.
It is related to the default ItemsPanel of the ListBox which is a VirtualizingStackPanel. It reuses the controls from the ItemTemplate of the ListBox and changes their DataContext when necessary to display different items. Unlike normal StackPanel, it does NOT create separate controls for different items.
For this to work, the items must refresh when the DataContext changes. And here's the actual problem. Because of this code:
if (mySource == null)
{
mySource = DataContext as Message;
}
the DataContext was actually loaded just once, and subsequent changes did not affect the UI. Removing the if and getting the DataContext every time it changes fixed the issue.

This may be a result of virtualization.
If your list is long enough, there is a reuse of controls for items in it.
Imagine you have a list with 1000 items in it, but the listbox only has 30 control instances that are re-used when you scroll up and down.
If this is the case, you will see the wrong behavior repeatin ever X items.
In order to solve this problem, I would recommend that instead of using DataContextChanged, you should expose a property as DependencyProperty.
In your data template, bind to this property and this will do the trick for you.
public bool ShowMyGrid
{
get { return (bool)GetValue(ShowMyGridProperty); }
set { SetValue(ShowMyGridProperty, value); }
}
public static readonly DependencyProperty ShowMyGridProperty =
DependencyProperty.Register("ShowMyGrid", typeof(bool), typeof(MyUserControl1), new UIPropertyMetadata(false, ShowMyGridCallback));
static void ShowMyGridCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myControl = d as MyUserControl1;
bool newVal = (bool)e.NewValue;
if (newVal)
{
myControl.MyGrid.Visibility = Visibility.Collapsed;
myControl.MyOtherGrid.Visibility = Visibility.Visible;
}
else
{
myControl.MyGrid.Visibility = Visibility.Visible;
myControl.MyOtherGrid.Visibility = Visibility.Collapsed;
}
}

Related

User Control Child elements sharing Value across multiple instances

I have made a User Control, FontSelector, that groups together a ComboBox for FontFamily Selection and three ToggleButtons for Bold, Italics, Underline options. I am having an issue with the ComboBox's SelectedItem property affecting all instances of that User Control within the same Window. For example, changing the ComboBox selection on one, will automatically change the other. For Clarity. I don't want this behavior. I am very surprised that a User Control is implicitly affecting another User Control.
XAML
<Grid x:Name="Grid" Background="White" DataContext="{Binding RelativeSource={RelativeSource AncestorType=local:FontSelector}}">
<ComboBox x:Name="comboBox" Width="135"
SelectedItem="{Binding Path=SelectedFontFamily}" Style="{StaticResource FontChooserComboBoxStyle}"
ItemsSource="{Binding Source={StaticResource SystemFontFamilies}}"/>
</Grid>
Code Behind
The CLR Property that the ComboBox's SelectedItem is Bound to. Code shown here is in the User Control Code Behind File, not a ViewModel.
private FontFamily _SelectedFontFamily;
public FontFamily SelectedFontFamily
{
get
{
return _SelectedFontFamily;
}
set
{
if (_SelectedFontFamily != value)
{
_SelectedFontFamily = value;
// Modify External Dependency Property Value.
if (value != SelectedFont.FontFamily)
{
SelectedFont = new Typeface(value, GetStyle(), GetWeight(), FontStretches.Normal);
}
// Notify.
RaisePropertyChanged(nameof(SelectedFontFamily));
}
}
}
The Dependency Property that updates it's value based on the Value of the ComboBox's SelectedItem Property. It effectively packages the FontFamily value into a Typeface Object.
public Typeface SelectedFont
{
get { return (Typeface)GetValue(SelectedFontProperty); }
set { SetValue(SelectedFontProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedFont. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedFontProperty =
DependencyProperty.Register("SelectedFont", typeof(Typeface), typeof(FontSelector),
new FrameworkPropertyMetadata(new Typeface("Arial"), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(OnSelectedFontPropertyChanged)));
private static void OnSelectedFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as FontSelector;
var newFont = e.NewValue as Typeface;
if (newFont != null)
{
instance.SelectedFontFamily = newFont.FontFamily;
}
}
EDIT
I think I may have figured out what is going on. I can replicate it by Binding the ItemsSource to the Following Collection View Source.
<CollectionViewSource x:Key="SystemFontFamilies" Source="{Binding Source={x:Static Fonts.SystemFontFamilies}}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="Source"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
You can then replicate the behavior by placing 2 ComboBoxes and Binding both of them to the CollectionViewSource. They will now, seemingly implicitly track each others SelectedItem. Even without Any Data Binding outside of ItemsSource. It would seem that the CollectionViewSource is somehow playing a part in what the SelectedItem is.
I'd make it a bit different. I'll introduce this solution using only a String, not FontFamily or FontWeight, since I have no VS here right now. (In order to have it working, please change the list of FontFamilies to a list of strings to bind them.)
Your selector UserControl:
- your xaml is ok (but you won't need the x:Name)
- the CodeBehind of the UserControl (later: UC) should change, we will solve it with binding. You should have a DependencyProperty, lets' call it SelectedFontFamily, which will represent the selected string from the ComboBox:
public string SelectedFontFamily
{
get { return (string)GetValue(SelectedFontFamilyProperty); }
set { SetValue(SelectedFontFamilyProperty, value); }
}
public static readonly DependencyProperty SelectedFontFamilyProperty = DependencyProperty.Register("SelectedFontFamily", typeof(string), typeof(YourUC), new PropertyMetadata(string.Empty));
The Window, which contains the UC:
- You should include the namespace of the UC's folder in the opening tag of the window, eg:
<Window
...
xmlns:view="clr-namespace:YourProjectName.Views.UserControls">
- the window's DataContext should have a property with public set option (feel free to implement INotifyPropertyChange on it):
public string FontFamily {get; set;}
- in the Window's xaml you would use the UC this way:
<view:YourUC SelectedFontFamily="{Binding FontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It's a two-way binding. You'll find the selected string as the value of the FontFamily property every time you change the SelectedItem.
Edit: you will need View Model class for the Window which is using the UserControl. Create it, make it implement the INotifyPropertyChanged interface, and set it as DataContext for your consumer window. WPF is not like WF, you can find more about it if you Google up "WPF MVVM" or something like that.
Found the problem. I was binding to a CollectionViewSource defined in Application Resources. Until now I was unaware that Binding to a CollectionViewSource will also affect the SelectedItem. The SelectedItem Data gets stored as part of the CollectionViewSource. Setting the IsSynchronizedWithCurrentItem property to False on the ComboBox solved the issue.
Here is an existing answer I have now Found.
Thanks

Datagrid sort, what happened to row?

I am coding a UserControl that can be use as a ScrollBar with color mark on it (like mostly all IDE).
My UserControl look
<UserControl x:Class="MarkedScrollBar" x:Name="Instance" ...>
<Grid Name="grd" >
<COLUMN/ROW DEF>...</>
<ScrollViewer Name="scrView" Grid.RowSpan="3" Grid.ColumnSpan="2" >
<my:IcuContentPresenter x:Name="presenter" Content="{Binding AdditionnalContent, ElementName=Instance}" ContentUpdated="presenter_ContentUpdated" />
</ScrollViewer>
<Canvas ...(Canvas place on the scrollbar) />
</Grid>
</UserControl>
On my UserControl, everything work fine. I can add content in my IcuContentPresenter (Extension of ContentPresenter to get 1 more event).
In my main window i use my MarkedScroolBar like this :
<my:IcuMarkedScrollBar Grid.Row="0" x:Name="bbb" Grid.Column="0" >
<my:IcuMarkedScrollBar.AdditionnalContent>
<my:IcuDataGrid ItemsSource="{Binding AAA, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Loaded="DataGrid_Loaded" Width="300" LoadingRow="DataGrid_LoadingRow" />
</my:IcuMarkedScrollBar.AdditionnalContent>
</my:IcuMarkedScrollBar>
Again, i have a DataGrid extension to get 1 event (Sorted event, code from here).
Now my problem is that when i sort my datagrid Inside my MarkedScrollBar, i want the mark to follow the content, that the mark is binding to.
First attempt, I try to link the mark with the control having a Dictionary Key=Mark, Value=Control. I was getting the DatagridRow that I wanted to have a mark for my datacontext.
First time the datagrid appear, my mark are all well place, but if I sort, my mark don't follow. I went into debugging and saw that there is a DataGridRow that contains my datacontext item, but it's not the one i save in my Dictionary.
End First attempt
Second attempt, with the DataGridRow changing when I sort, I thought that if I was linking my DataContext item to my mark it would fix the bug.
So now, when I sort my datagrid, I try to find the datagridrow that contains my datacontext item. But it does find anything.
But, when i reexecute the same code manually with a button in the window, it's work. All my mark appear at the good position.
End second attempt
Brief explanation of my extension :
DataGrid : I use it to get the Sorted event, because in Sorting event the row where not already move.
ContentPresenter : I add event on it to get modification to the content, but not to the Property (like datagrid sorting). For that i needed to check the type of the content, so i don't support everything for now.
My code for the ContentPresenter is that :
public class IcuContentPresenter : ContentPresenter
{
public event EventHandler<ControlEventArgs> ContentUpdated;
public IcuContentPresenter()
{
this.Loaded += OnLoaded;
}
protected void OnLoaded(object p_oSender, RoutedEventArgs p_oEvent)
{
Type t = this.Content.GetType();
if (t == typeof(IcuDataGrid))
{
IcuDataGrid oControl = this.Content as IcuDataGrid;
oControl.Sorted += CallEvent;
}
}
void CallEvent(object sender, EventArgs e)
{
if (ContentUpdated != null)
{
ContentUpdated(this, new ControlEventArgs(Content.GetType()));
}
}
public UIElement FindVisualElementForObject(object p_oObject)
{
UIElement oTheOne = null;
if (Content.GetType() == typeof(IcuDataGrid))
{
oTheOne = FindInDatagrid(p_oObject);
}
return oTheOne;
}
private UIElement FindInDatagrid(Object p_oObj)
{
DataGrid oGrid = Content as DataGrid;
var a = (DataGridRow)oGrid.ItemContainerGenerator.ContainerFromItem(p_oObj);
return a;
}
//----------------------------------------------------
// Inner class
//----------------------------------------------------
public class ControlEventArgs : EventArgs
{
public ControlEventArgs(Type value)
{
ControlType = value;
}
public Type ControlType { get; set; }
}
}
To support a control i will have to add them here. On the load of this control, I check the content, if the content is my Datagrid on the event Sorted i call the my Event ContentUpdated to be able to update my mark. But at this moment, it's like my datagrid contains no row with my datacontext item.
When virtualization is enabled, row elements will only be generated for data items which are actually in view. You will need to associate the marks with the underlying data items (row objects) rather than the DataGridRow elements. The Items property on the DataGrid should have the rows in their correctly sorted positions.

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.

WP7.8: Bound items in scrollbox updated with wrong data

Overview
I have an application, that displays data from an observable collection. The observable collection is (in this debugging setting) created and instanciated only once, then the values stay the same.
The main view of the application contains a ListBox that is bound to said observable collection:
<ListBox x:Name="MainListBox" ItemsSource="{Binding Items}" SelectionChanged="MainListBox_SelectionChanged" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel MinWidth="456" MaxWidth="456" Background="White" Margin="0,0,0,17">
<sparklrControls:SparklrText Post="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<!-- Workaround used to stretch the child elements to the full width -> HorizontalContentAlignment won't work for some reason... -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
The child items are bound to a UserControl. This UserControl implements a DependancyProperty which the child elements are bound to:
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(object), new PropertyMetadata(textPropertyChanged));
private static void postPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SparklrText control = d as SparklrText;
control.Post = (ItemViewModel)e.NewValue;
}
Binding to the post property configures other variables via the getter of the Post property
public ItemViewModel Post
{
get
{
return post;
}
set
{
if (post != value)
{
this.ImageLocation = value.ImageUrl;
this.Username = value.From;
this.Comments = value.CommentCount;
this.Likes = value.LikesCount;
this.Text = value.Message;
post = value;
}
}
}
This setter configures other which in turn set up elements in the user control. Nothing in the user control is bound, the few updates are done with direct access to the respective Content/Text properties. ImageLocation performs an asynchronous download of an image with
private void loadImage(string value)
{
WebClient wc = new WebClient();
wc.OpenReadCompleted += (sender, e) =>
{
image = new BitmapImage();
image.SetSource(e.Result);
MessageImage.Source = image;
};
wc.OpenReadAsync(new Uri(value));
}
Issue
When I scroll down in the list box and back up, the setter of Post is executed when the owning element comes back into view. The problem: value is a different instance of ItemViewModel. The ListBox ItemsSource is not accessed in any way from outside the class. When scrolling back up, it seems like the wrong Items are bound to the elements, resulting in distorted designs. Are there any issues with the Binding that cause this?
The issue was caused by the ListBox. Elements that are scroll out of view are recycled and appended on the other side. In the code above, a asynchronous operation did not check if the result was still valid, causing wrong display data.

Listview virtualization and deselecting all items [duplicate]

I have implemented a selection pattern similar to the one described in this post using a ViewModel to store the IsSelected value, and by binding the ListViewItem.IsSelected to the ViewModel IsSelected:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</ListView.ItemContainerStyle>
It works in general, but I encounter a severe problem. By using the a VirtualizingStackPanel as the panel in the list view, only the visible ListViewItem are getting created. If I use "Ctrl+A" to select all items, or by using shortcut combination such "Shift+Ctrl+End" on the first item, all items get selected, but for the non visible items, the ViewModel does not get its IsSelected set to true. That is logical, because if the ListViewItem are not created, the binding can't work.
Anybody experienced the same issue, and found a solution (apart from not using a VirtualizingStackPanel)?
I found another way of handling selection in the MVVM pattern, which solved my issue. Instead of maintaining the selection in the viewmodel, the selection is retrieved from the ListView/ListBox, and passed as a parameter to the Command. All done in XAML:
<ListView
x:Name="_items"
ItemsSource="{Binding Items}" ... />
<Button
Content="Remove Selected"
Command="{Binding RemoveSelectedItemsCommand}"
CommandParameter="{Binding ElementName=_items, Path=SelectedItems}"/>
in my ViewModel:
private void RemoveSelection(object parameter)
{
IList selection = (IList)parameter;
...
}
In my case, I ended up solving this by deriving a ListBoxEx class from ListBox, and adding code to respond to selection changes, enforcing the selection state on the item view models:
private readonly List<IListItemViewModelBase> selectedItems = new List<IListItemViewModelBase>();
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
bool isVirtualizing = VirtualizingStackPanel.GetIsVirtualizing(this);
bool isMultiSelect = (SelectionMode != SelectionMode.Single);
if (isVirtualizing && isMultiSelect)
{
var newSelectedItems = SelectedItems.Cast<IListItemViewModelBase>();
foreach (var deselectedItem in this.selectedItems.Except(newSelectedItems))
{
deselectedItem.IsSelected = false;
}
this.selectedItems.Clear();
this.selectedItems.AddRange(newSelectedItems);
foreach (var newlySelectedItem in this.selectedItems)
{
newlySelectedItem.IsSelected = true;
}
}
}
Apart from not using VirtualizingStackPanel, the only thing I can think of is to capture those keyboard shortcuts and have methods for modifying a certain range of your ViewModel items so that their IsSelected property is set to True (e.g., SelectAll(), SelectFromCurrentToEnd()). Basically bypassing the Binding on ListViewItem for controlling the selection for such cases.

Categories