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.
Related
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">
I have a ComboBox in WPF. The ComboBox is inside of a grid, and the grid's DataContext is bound to the SelectedItem of a ListView. The ItemsSource of the ComboBox is set to a StaticResource, located in the window resources. The ItemsSource does not change. I have tried to use both SelectedValue and SelectedItem but both of them cause the same issue for me. The issue is that when the SelectedItem of the ListView is changed, the ComboBox is actually setting the property value from the PREVIOUSLY selected item, to the property value of the NEWLY selected item. Clearly I am doing something wrong, because I have used comboboxes many times in the past without this issue. I have scoured the web and can't find an answer. The closest, most similar question I found was: Strange behaviour (or bug?) with ComboBox in WPF when changing DataContext and having bound ItemsSource and SelectedItem
But it doesn't seem to have a solution. The solutions listed in comments did not work for me.
I created SelectionChanged events for both the ListView and the ComboBox and set breakpoints at each of them and the property that is being set. The property is actually being set BEFORE either one of those are triggered. So even if I wanted to create some hack workaround, I couldn't.
For the record, the ComboBox functionality works perfectly fine. When an object is selected in the ListView, I can see the Template name property, as I should, and the list of items is correct. If I manually change the selected item, the property is changed to a new item, just like it should. The problem is that when I change the selected item in the ListView, the "Template" property of the newly selected object is being set to the "Template" property of the previously selected object. So the combobox is changing before anything else.
The xaml for the ListView and ComboBox are below.
<ListView x:Name="my_ListBox" FlowDirection="RightToLeft"
Margin="5" Grid.RowSpan="2" SelectedIndex="0"
ItemsSource="{Binding Source={StaticResource myList}}"
DisplayMemberPath="Name"
SelectionChanged="my_ListBox_SelectionChanged"/>
<Grid DataContext="{Binding ElementName=my_ListBox, Path=SelectedItem}">
<ComboBox Name="comboBox_myTemplate"
ItemsSource="{Binding Source={StaticResource myTemplatesList}}"
SelectedValue="{Binding Template}"
SelectionChanged="comboBox_myTemplate_SelectionChanged"
DisplayMemberPath="Name" FontSize="20" Margin="5"/>
</Grid>
If I set "IsSynchronizedWithCurrentItem="True"" in the ComboBox the problem is resolved. If someone wants to explain what exactly that is doing and how it works I'd love to hear it. Thanks.
I try to bind a ComboBox to a collection:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding YAxes}"
SelectedItem="{Binding SelectedYAxis, Mode=TwoWay}"
DisplayMemberPath="AxisTitle"
SelectedValuePath="AxisTitle"/>
Everything is fine, except Text of this ComboBox. On selection of item, the setter on SelectedYAxis fires and notifies, that property has been changed:
private IAxis _selectedYAxis;
public IAxis SelectedYAxis
{
get => _selectedYAxis;
set
{
_selectedYAxis = value;
OnPropertyChanged(nameof(SelectedYAxis));
}
}
but the text on ComboBox never changes to the selected items AxisTitle. How to display an AxisTitle of SelectedItem as a text of ComboBox?
UPD: Text is never shown, even if it's set explicitly:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding XAxes}"
SelectedItem="{Binding SelectedXAxis, Mode=TwoWay}"
DisplayMemberPath="AxisTitle"
Text="Asdasd"/>
It doesn't set the text of ComboBox to "Asdasd".
UPD 2: I've changed the things to use DataTemplate, but this didn't work as well:
<ComboBox Margin="4 0 2 0"
ItemsSource="{Binding YAxes}"
SelectedItem="{Binding SelectedYAxis, Mode=TwoWay}"
ItemTemplate="{StaticResource AxisCBTextTemplate}"/>
And the resource section above:
<DataTemplate x:Key="AxisCBTextTemplate">
<TextBlock Text="{Binding AxisTitle}"/>
</DataTemplate>
UPD 3
An illustration to what do I mean:
The task of displaying some selected text should be trivial, but it has difficulties.
I've found the root cause of this issue. Each IAxis (it is a SciChart axis object) from XAxes and YAxes is already dispayed on the graph (i.e. bound). Binding them to other controls (like ListBox) causes an exception: "Must disconnect specified child from current parent Visual before attaching to new parent Visual.", I found it out while trying to bind them to ListBox.
Seemes like ComboBox catches such exceptions and doesn't output StackTrace for any case. In my case this exception was wrapped into NullReferenceException and occurred only on click on a ComboBox, that has no ItemTemplate set. Though I may not be fully correct in details, replacing XAxes and YAxes with collections of strings solves this issue.
I have a ListView that contains several types of custom UserControls.
The project requires that some of them must be non-clickable, so I would like to disable them, but JUST THEM.
Those items will be enabled/disabled depending on the value of a custom property.
I've tried to set the ListViewItem.IsEnabled property to false, but it ain't worked, and the other solutions I've found around make no sense to me...
I let a sample of the code:
XAML
<ListView x:Name="homeLW"
Margin="0,5,0,0"
ItemClick="homeLW_ItemClick"
IsItemClickEnabled="True"
HorizontalAlignment="Center"
ItemsSource="{Binding Source}">
Where Source is a ObservableCollection<UserControl>.
The problem is that I can't get the items of the ListView as ListViewItems, but as the UserControl type:. When executing this:
foreach(ListViewItem lwI in homeLW.Items)
{
//CODE
}
I get:
System.InvalidCastException: Unable to cast object of type
UserControl.Type to type Windows.UI.Xaml.Controls.ListViewItem.
Anyone know how could I make it?
Thanks in advance :)
foreach(var lwI in homeLW.Items)
{
ListViewItem item =(ListViewItem)homeLW.ContainerFromItem(lwI);
item.IsEnabled = false;
}
When on load all ListViewItems wont be loaded because of Virtualization. So you get Null when try to get container from item. Workaround would be switching off the virtualization. But it will have performance effects. Since you confirmed that it wont be having more than 20 items,I ll go ahead and add the code
<ListView>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
To add onto LoveToCode's answer, if you want to disable the selected items on load and not turn off Virtualization, you'll need to fire the code when the UIElement is loaded. Otherwise, you'll get a System.NullReferenceException. The reason for this is because the Framework Element hasn't been loaded to reference the ListView Container.
homeLW.Loaded += DisableSelectedItemsOnLoad()
private void DisableSelectedItemsOnLoad()
{
foreach(var lwI in homeLW.Items)
{
ListViewItem item =(ListViewItem)homeLW.ContainerFromItem(lwI);
item.IsEnabled = false;
}
}
I have a UserControl I've written to display a few properties from a custom object. There are multiple instances of these objects so I have an ObservableCollection of them so I can set them as an ItemsSource binding to a ListView. Now I can get an instance of this UserControl show up for each instance of my class in my ListView.
The problem is I don't really want the behavior of a ListView. I don't want the user to be able to select the entire UserControl. In fact, the user should be able to select individual elements in the UserControl.
I thought about just using a StackPanel to put these UserControls in, but it doesn't have an ItemesSource property. Is there an easy way to make this happen?
Replace your ListView with an ItemsControl and set the ItemTemplate to a suitable DataTemplate for your objects. You can set the ItemsPanel if you want to change how the panel lays out the items.
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
See this example