Disable certain ListViewItem depending on custom property UWP - c#

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;
}
}

Related

wpf UI freezes when opening Combobox with a large number of items

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.

How to bind my ObservableCollection two way to a ListView Extension WinRT Xaml Toolkit

I have a Windows 8.1 application with a ListView and I am using ListViewExtensions from WinRt Xaml Toolkit(Obtained latest from Nuget) to bind BindableSelection
Here is my XAML
<ListView
ItemsSource="{Binding AllItems}"
SelectionMode="Multiple"
ext:ListViewExtensions.BindableSelection="{Binding SelectedItems, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In my ViewModel I have the following ObservableCollection which I have bound my xaml to
private ObservableCollection<string> _SelectedItems;
public ObservableCollection<string> SelectedItems
{
get { return _SelectedItems; }
set
{
if (value != _SelectedItems)
{
_SelectedItems = value;
NotifyPropertyChanged("SelectedItems");
}
}
}
I have put breakpoints on the get and set of my ObservableCollection. The get will be called as soon as my View loads, but the set is never called even though I select multiple items of my ListView.
Am I doing something wrong.
I would be very glad if someone can point me in the right direction.
Thanks in Advance.
Realized my mistake. I have never created the object for ObservableCollections SelectedItems.
One should create the object for the ObservableCollection at some point otherwise the XAML will be binding to a null object reference which obviously cannot be updated.
Here is how you instantiate the ObservableCollection.
SelectedItems = new ObservableCollection<MyItems>();
However I am still unable to hit the breakpoint of the set function of the ObservableCollection. I believe that this is the default behavior of the Observable. Would be glad if someone can comment on that.
Nevertheless the problem for this particular question is solved.
Thanks

WPF ListBox and large amound of data

I need to display a large amount of data (thousands of items) in a ListBox however it takes some times and the UI is not responsive until the whole items are displayed in the ListBox.
The ItemsSource of the ListBox is bound the an CollectionView.
I know the benefit of using the VirtualizingStackPanel but I insist to use a WrapPanel.
I've searched over the internet and found some VirtualizedWrapPanels but they have the same issue which is they don't allow the VirtualizingWrapPanel to grow to whatever size it likes and I have to set both Width and Height for them.
Now I need to know what other options are out there for me to do the job? What can I do so that the ListBox loads and displays this large amount quickly.
Please let me know if I haven't explained m
y issue clearly.
Any help is appreciated. Thanks in advance.
Edit
This is the relevant code
public ObservableCollection<T> Items
{
get { return _items; }
set
{
if (_items == value)
return;
_items = value;
OnPropertyChanged(() => Items);
}
}
I instantiate the ObservableColelction in the LoadData method of the ViewModel
_items = _service.Select();
and the CollectionView is instantiated in this way
ICollectionView cv = new CollectionViewSource() { Source = Items }.View;
and the xaml code of the ListBox
<ListBox
x:Name="Items"
ItemsSource="{Binding CollectionView}"
Padding="10,10,10,10"
SelectionMode="Single"
VerticalAlignment="Top"
IsSynchronizedWithCurrentItem="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical" Height="480" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
I am not sure if this is applicable or you are looking for this, however virtualizing the data is one more solution you can look into. there are some very good posts describing this. these 2 blog/post seems to be very good. Please have a look Here[http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization] and here[http://www.zagstudio.com/blog/498#.U7a6OvldV1F].

Get Listboxitem from listbox

Hi this should be faily simple, however I don't know what I am doing wrong. I've been looking all over the internet seeing people make this work, even followed the tutorial on MSDN still nothing has worked for me.
I want to Iterate over a ListBox, and get the ListBoxItems so I can find the DataTemplate that I have added to it.
This is my code behind.
private void SetListBoxDataTemplate(ListBox MyListBox)
{
try
{
foreach (CustomDataTemplateObject dataobject in MyListBox.Items)
{
ListBoxItem lbi = (ListBoxItem)(MyListBox.ItemContainerGenerator.ContainerFromItem(dataobject));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(lbi);
DataTemplate dt = myContentPresenter.ContentTemplate;
TextBlock tb = (TextBlock)dt.FindName("ListBoxItemTextBlock1", myContentPresenter);
ComboBox cb = (ComboBox)dt.FindName("ListBoxItemComboBox1", myContentPresenter);
tb.Text = dataobject.Text;
cb.ItemsSource = dataobject.ListColors;
}
}
catch (Exception ex)
{
MessageBox.Show(""+ex);
}
}
XAML looks like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Name="ListBoxItemTextBlock1" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox Name="ListBoxItemComboBox1" />
</StackPanel>
</DataTemplate>*
<StackPanel>
<ListBox Name="ListBoxTest1" ItemTemplate="{DynamicResource ListBoxItemDataTemplate1}" />
</StackPanel>
I have tried with setting my itemtemplate to static to see if it works, and the method i'm calling from code behind, is called after I have populated my ListBoxs
My dataobject is NOT null, however when i call the line in my code behind, my lbi, ends up being null.
Any suggestions? thanks in advance!
FIRST UPDATE
This problem only occurs if i call the method in my constructor, so perhaps it's because it hasn't initialized the full group element section yet. However I want to do this as soon as possible. Am I perhaps forced to do it in a WindowLoaded event?
SECOND UPDATE
Code updated, Rachel's answer worked for iterating over my ListBoxItems, however the Listbox Has not fully rendered since i'm unable to reach the Datatemplate at this time. So MyListBox_GeneratorStatusChanged is not working for this problem, but it does get the ListBoxItems.
WPF's main thread runs items at different priority levels. Code that runs in the Constructor all gets run at Normal priority, while things like rendering the ListBox and it's items run at the Render priority level, which occurs after all Normal priority operations have finished.
This means that your entire Constructor gets run (including SetListBoxDataTemplate()) before your ListBox is even rendered and the items get generated.
If you want to run some code after the items are generated, use the ItemsContainerGenerator.StatusChanged event
// Constructor
MyListBox.ItemContainerGenerator.StatusChanged += MyListBox_GeneratorStatusChanged;
...
void MyListBox_GeneratorStatusChanged(object sender, EventArgs e)
{
// return if containers have not been generated yet
if (MyListBox.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
return;
// remove event
MyListBox.ItemContainerGenerator.StatusChanged -= MyListBox_GeneratorStatusChanged;
// your items are now generated
SetListBoxDataTemplate(MyListBox);
}
What are you trying to accomplish with this method anyways? It is a bit unusual for WPF, and there may be a much better WPF way of accomplishing your task.
Updated based on new code added to Question
A much better method of setting your Text and ItemsSource properties is to make use of WPF's data bindings.
Your DataTemplate should look like this:
<DataTemplate x:Key="ListBoxItemDataTemplate1">
<StackPanel Orientation="Horizontal">
<Border BorderBrush="Black" BorderThickness="1 1 0 1" MinWidth="50">
<TextBlock Text="{Binding Text}" Background="{Binding ElementName=ListBoxItemComboBox1, Path=SelectedValue}" >
</TextBlock>
</Border>
<ComboBox ItemsSource="{Binding ListColors}" />
</StackPanel>
</DataTemplate>*
A DataTemplate is like a cookie cutter. It's used to make the UI objects, but is not part of the UI object itself. All it does is tell WPF that "When you go to render this object, render it using this XAML". So the way your XAML gets rendered is
<ListBoxItem>
<StackPanel>
<Border>
<TextBlock Text="{Binding Text}" />
</Border>
<ComboBox ItemsSource="{Binding ListColors}">
</StackPanel>
</ListBoxItem>
In addition, the DataContext behind your ListBoxItem is the item from the collection bound to ListBox.ItemsSource, which based on your code should be CustomDataTemplateObject. That allows the bindings from the DataTemplate to work
If you're new to WPF and struggling to understand how exact the DataContext works, I'd recommend reading this article of mine: What is this "DataContext" you speak of?.
To summarize, WPF has two layers to an application: the UI layer and the Data Layer (DataContext). When you perform a basic binding like above, you are pulling data from the data layer into the UI layer.
So your ListBoxItem has a data layer of CustomDataTemplateObject, and the TextBlock.Text and ComboBox.ItemsSource bindings are pulling data from the data layer for use in the UI layer.
I'd also highly recommend using a utility like Snoop which lets you view the entire Visual Tree of a running WPF application to see how items get rendered. Its very useful for debugging or learning more about how WPF works.
You're confusing two jobs and mixing them into one. First, get access to the ListBoxItem:
private void SetListBoxDataTemplate(ListBox MyListBox)
{
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
}
}
Now you can get the DataTemplate from the ListBoxItem:
foreach (ListBoxItem listBoxItem in MyListBox.Items)
{
ContentPresenter presenter = FindVisualChild<ContentPresenter>(listBoxItem);
DataTemplate dataTemplate = presenter.ContentTemplate;
if (dataTemplate != null)
{
// Do something with dataTemplate here
}
}
The FindVisualChild method can be found in the How to: Find DataTemplate-Generated Elements page on MSDN.
UPDATE >>>
To answer your edit, yes, the constructor will be too early to try to access these DataTemplates because the Framework won't have applied them to all of the objects by then. It is best to use the FrameworkElement.Loaded Event to do these kinds of things, as that is the first event that can be called after the controls have all been initialised.

Silverlight ItemsControl behavior: how do i get an item i click on?

I'm creating a behavior for an ItemsControl with the purpose of selecting the item i click on (and adding it to a list of selected items).
So it's easy to get all the items:
hours = AssociatedObject.ItemsSource as List<Hour>;
and of course i could write hours[0].Selected = true;
but then I've got a mouse event, that i tried writing something like this:
void AssociatedObject_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
hour = sender as Hour;
}
the problem is, it's not working like i expected... the sender is not an Hour, it's an ItemsControl.
and I've got no indication as to which hour was clicked.
so what should i do to get the hour?
Edit
My code works like this:
there's an ItemsControl bound to a list of Days.
each day has a list of hours.
and to represent that, there is an inner ItemControl bound to (day.)Hours.
and to represent each hour, there's a border.
looks like this:
<ItemsControl x:Name="daysPanel" Grid.Column="1" ItemsSource="{Binding Days}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl x:Name="dayHours" ItemsSource="{Binding Hours}" Grid.Row="1">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="dayHourBorder" Tag="{Binding}" Height="30" BorderBrush="#B0B6BE" Width="193" BorderThickness="1,0,1,1" Background="{Binding Path=Selected, Converter={StaticResource boolToColorConverter}}" >
thanks everyone for trying to help, but i found the right way to do it.
i knew there had to be simple way to get the UI element that was clicked on, there just had to be one!
and there was!
instead of working with the sender, you just have to do:
e.OriginalSource
that got me the border (and the hour that's bounded to it).
so it's as "simple" as:
(e.OriginalSource as Border).DataContext as Hour
VisualTreeHelper may be useful for you.
You can use to get all elements at point, where mouse clicked and get your Border. Its tag is binded to Hours, so you can get it.
Get the ItemsControl of a DataTemplate from SO and VisualTreeHelper from http://blogs.msdn.com must help you.
I believe this should work - the sender will be the UI element that sent the click event, and since you're using ItemsSource to set it up, each item's DataContext will be what you're after:
hour = (sender as FrameworkElement).DataContext as Hour
The ItemsControl by it self does not provide any properties or events for the currently selected item. You have to use a class, such as ListBox, that derived from ItemsControl, respectively from Selector, cause this contains the functionality for item selection (SelectedItem, SelectedIndex property,...).
I had to do something similar to what you're doing. I wanted to make the current row the selected one.
The easiest way is to use an ICollectionView MSND link and bind THAT to the ItemsControl.
You can then add a behaviour (if it's not there already) that listens to the selected event and changes current accordingly.
Then you only need to hook up the CurrentChanged event on your ViewModel and you're completely decoupled from the UI :)
Let me know if it's what you're looking after and I may try and get some of my code as an example.

Categories