I've been running into a strange issue with a listbox within a UserControl with WPF and MVVM. The listbox has its items bound via a ViewModel and an ObservableCollection<T>, the style property IsSelected is bound to a property Selected inside the individual items.
[...]
<ListBox ItemsSource="{Binding Path=SQLObjects}" FontFamily="Consolas" Margin="5,0,5,5" DisplayMemberPath="Name" SelectionMode="Extended" SelectedItem="{Binding Path=SelectedSQLObject}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" BasedOn="{StaticResource MetroListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Selected, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
</ListBox>
[...]
When a user selects a single or multiple items (with ctrl or shift) the properties are updated as expected. But selecting all items with ctrl + a only updates the property Selected of the items visible inside the scroll area of the listbox.
in the screenshot all items are selected with ctrl + a but only the items currently visible have their property updated. When scrolling down in the listbox without changing the selection, all items are updated as they come into view.
Could this be caused by MahApps.Metro?
The ViewModel is bound to the UserControls Datacontext inside the xaml in the main window.
[...]
<TabItem Header="Trigger">
<userControls:ObjectControl>
<userControls:ObjectControl.DataContext>
<viewmodel:TriggerObjectViewModel DialogCoordinator="{x:Static local:MainWindow.Coordinator}" />
</userControls:ObjectControl.DataContext>
</userControls:ObjectControl>
</TabItem>
[...]
All other TabItems use the same usercontrol and have the same issue.
I would like to not use a workaround (e.g. scrolling trought the whole list when the user selects items outside of the current view), is there any kind of property I could set for WPF to update all items inside a collection, regardless if they are visible or not?
I suspect this is due to virtualization. The UI items don't exist until they are scrolled into view.
Try turning off virtualization on your list control:
<ListBox ItemsSource="{Binding Path=SQLObjects}"
VirtualizingPanel.IsVirtualizing="False">
Related
I am working on a WPF Application where i have a combobox with ItemsSource binded to a property of 5000 records coming from database. The problem is that when i click the dropdown arrow of combobox the UI not responding or combobox is taking too much time to respond. I searched it but nothing worked for me.
here is the code:
<ComboBox IsEditable="True" ItemsSource="{Binding List,Mode=OneWay}" DisplayMemberPath="name" SelectedValue="{Binding SelectedItem,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
and the property
private ObservableCollection<Object> _List = new ObservableCollection<Object>();
public ObservableCollection<Object> List
{
get { return _List ; }
set { _List = value; OnPropertyChanged("List"); }
}
Edit:
here is the code that loads data inside the constructor
public FormVM()
{
List = new ObservableCollection<Object>(db.cat.ToList());
}
You have to enable UI virtualization.
Currently UI virtualization is disabled for your ComboBox!
Controls like ListBox or ListView have this feature enabled by default.
Other controls that extend ItemsControl like ComboBox have to enable it explicitly.
To enable UI virtualization
The ItemsPresenter (or any Panel with Panel.IsItemsHost set to True) of the ItemsControl must be the child of a ScrollViewer.
This is already the case for the ComboBox.
The ScrollViewer must be configured to scroll by items (logical units) instead of pixels (physical units) by setting the attached ScrollViewer.CanContentScroll property to True.
ItemsControl must have its ItemsPanel set to a VirtualizingStackPanel.
The virtualization mode of the VirtualizingPanel must be enabled by setting the attached property VirtualizingPanel.IsVirtualizing to True.
Example
<ComboBox VirtualizingPanel.IsVirtualizing="True"
ScrollViewer.CanContentScroll="True">
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
</ComboBox>
Further improvements can be achieved by enabling deferred scrolling:
<ComboBox ScrollViewer.IsDeferredScrollingEnabled="True" />
Satisfying one of the following conditions will make UI virtualization impossible:
Item containers are added directly to the ItemsControl. For example,
if an application explicitly adds ListBoxItem objects to a ListBox,
the ListBox does not virtualize the ListBoxItem objects.
Item containers in the ItemsControl are of different types. For
example, a Menu that uses Separator objects cannot implement item
recycling because the Menu contains objects of type Separator and
MenuItem.
Setting CanContentScroll to false.
Setting IsVirtualizing to false.
If you have followed every constraint then UI virtualization does work. You then have a problem which is not related to UI virtualization. If yoou would set up a new empty project with just a ComboBox you should encounter no issues.
I create a project using MVVM pattern.
In my View I create comboBoxes.
In ViewModel I create ObservableCollection for ItemsSource for comboBoxes with string values:
public ObservableCollection<string> ComboBoxItems
{
get; set;
}
In ViewModel constructor I create list of Models (foreach comboBox).
My Model class have only two properties: SelectedComboBoxItem and IsEnabledComboBoxItem.
I want to have a logic like if I select one item in one comboBox it shoud be disable in this comboBox and in all others. How could I do this with Binding?
Now my xaml code look like this, but it disabled only selected comboBox item and only in one comboBox, where he was called from:
<ComboBox ItemsSource="{Binding ComboBoxItems}"
SelectedItem="{Binding SelectedComboBoxItem }" IsEditable="True">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding IsEnabledComboBoxItem}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
To enable/disable comboBox items i used converter for them in xaml,
where passed all selected values from all comboBoxes (collection in ViewModel). In my converter class I did the check I needed and return a bool value(is item in collection in ViewModel or not). Maybe this helps somebody.
I have a ComboBox setup to display a bound list of ApplicationNames. The list type the ComboBox is bound to is ObservableCollection<Apps>. I am using a CollectionViewSource to do the sorting as so:
<CollectionViewSource
x:Key="Apps"
Source="{Binding Path=Apps}"
>
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="AppNames" Direction="Ascending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
The ComboBox is written as:
<ComboBox
SnapsToDevicePixels="True"
Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Grid.RowSpan="1"
DataContext="{StaticResource Apps}"
ItemsSource="{Binding}"
ItemContainerStyle="{StaticResource ApplicationItemStyle}"
ItemTemplate="{StaticResource applicationComboTemplate}"
>
</ComboBox>
Now when I open the View that houses this combobox all the application names display properly in the combobox and are sorted. The issue I am having is when I close that view and then re-open the view. When I re-open the view everything loads exactly as it did the first-time, in the way of how I get the ObervableCollection, but this time around nothing shows up in the ComboBox. Also, in the ViewModel I do clear the collection and remove all registered events when the View and ViewModel close.
Here is where I close:
void CleanUpApps()
{
if (this.Apps != null)
{
foreach (var app in this.Apps)
app.Dispose();
this.Apps.Clear();
this.Apps.CollectionChanged -= OnAppsCollectionChanged;
}
}
If I copy {Binding Path=Apps} from the CollectionViewSource and place it over {StaticResource Apps} in the ComboBoxes DataContext, everything works, except the sort now of course, the first time I open the view and every time after.
I am trying to find out what I am doing wrong in the CollectionViewSource, that is causing the second time around loading the view to not have any application names display in the combobox.
Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.
Why are ListBoxItems declared within the XAML itself not affected by the DataTemplate? The template is fine when I bind a source, and that's what I'll be doing ultimately, but just wondering why the declared items aren't styled.
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding}" Foreground="Red"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBoxItem>LBI 1</ListBoxItem>
<ListBoxItem>LBI 2</ListBoxItem>
</ListBox>
Here LBI 1 and LBI 2 are not red but if I was to use ListBox.ItemSource and bind a list, the items are red.
"When you set an ItemTemplate on an ItemsControl, the UI is generated as follows (using the ListBox as an example):
During content generation, the ItemsPanel initiates a request for the ItemContainerGenerator to create a container for each data item. For ListBox, the container is a ListBoxItem. The generator calls back into the ItemsControl to prepare the container.
Part of the preparation involves the copying of the ItemTemplate of the ListBox to be the ContentTemplate of the ListBoxItem.
Similar to all ContentControl types, the ControlTemplate of a ListBoxItem contains a ContentPresenter. When the template is applied, it creates a ContentPresenter whose ContentTemplate is bound to the ContentTemplate of the ListBoxItem.
Finally, the ContentPresenter applies that ContentTemplate to itself, and that creates the UI."
As you can see above, the datatemplate is used only for newly generated items. Also data templates are generally used to describe/present "the visual structure" of data - your ListBoxItems are already described as ListBoxItems, so they use that template... Hope this makes sense...
You need to apply Style. DataTemplate is used to apply template to Data which is bound to listBox. Hence not applying to the items which are directly added as child within XAML.
<ListBox>
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Foreground" Value="Red"/>
</Style>
</ListBox.Resources>
<ListBoxItem>LBI 1</ListBoxItem>
<ListBoxItem>LBI 2</ListBoxItem>
</ListBox>
Because LB1 and LB2 as in the example are not part of your data template. When you bind some data to the ItemsSource of your ListBox, those items are displayed according to the data template.