Some items in the ListView control will be selectable and have normal text.
Some items however, although included in the ListView as items, will be unselectable/unclickable and 'greyed-out'.
In Windows-Store-Apps we have the ability to select Single/Multiple/None items in a ListView. But how can make certain items at certain indexes unselectable/unclickable and 'greyed-out', in code mainly?
I managed to access the Item of the ListView at a certain index:
myListView.ItemContainerGenerator.ContainerFromIndex(i)
But I couldn't find any option to customize its selected event handler.
Any idea how to achieve that?
In Single selection mode.
First Add a boolean property to class of binding type which defines which items are clickable like this
class TestClass
{
Boolean IsClickAllowed{get;set;}
string name{get;set;}
}
then create a source list of TestClass type and set it as itemssource of Listview like this
var TempList=new List<>()
{
new TextClass(){IsClickAllowed=false,name="First Item"},
new TextClass(){IsClickAllowed=true,name="Second Item"},
new TextClass(){IsClickAllowed=false,name="Third Item"},
};
MyList.ItemsSource=TempList;
and for greying out Set Different DataTemplate for nonClickable items implementing DataTemplateSelector and finally for click handle in ItemClick event. You need to set IsItemClickEnabled as true.
private void MyList_ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem as TestClass;
if (item != null){
if(item.IsClickAllowed){
//Do Stuff here
}else
{
//Do Nothing
}
}}
Hope it helps.
I have found a solution:
I have override the ListView control and create a StripedListView. Then by overriding the PrepareContainerForItemOverride, which is responsible for the setting up the ListViewItem control after it’s be created, you could modify the background color and set the ItemListView.isEnabled option to false:
public class StripedListView : ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
base.PrepareContainerForItemOverride(element, item);
var listViewItem = element as ListViewItem;
if (listViewItem != null)
{
var index = IndexFromContainer(element);
if (Words.arrayW[index].Length > 0)
{
listViewItem.Foreground = new SolidColorBrush(Colors.Black);
}
else
{
listViewItem.Foreground = new SolidColorBrush(Colors.Gray);
listViewItem.IsEnabled = false;
}
}
}
}
In Xaml:
<controls:StripedListView x:Name="letterListView" ItemsSource="{Binding}">
<controls:StripedListView.ItemTemplate>
<DataTemplate>
etc...
</DataTemplate>
</controls:StripedListView.ItemTemplate>
</controls:StripedListView>
Related
I'm using a WPF ListView to show and select/deselect some items but there are some conditions that user cannot select/deselect an item and I should handle it in the code behind.
I tried to use ListView.SelectionChanged event to handle it but the problem is that when I change the selected item in the code behind (select it again if it was deselected or the other way), the event triggers again and I don't want that.
What is the best way to set conditions on select/deselect ListView item?
I tried to solve it using ListView.PreviewMouseLeftButtonDown event instead of ListView.SelectionChanged. But I wanted to know if there is a better way?
There might be other WPF controls (especially third party ones) that can handle this a little more gracefully. Additionally you can hook into the style of the ListView to alter the mouse-over behavior.
As a crude approach; however, I was able to accomplish what you're looking for by using the SelectionChanged event to effectively "undo" any selections we deem invalid.
First a simple ListView in xaml:
<ListView
x:Name="ItemsLv"
SelectionChanged="ItemsLv_SelectionChanged"
SelectionMode="Single" />
The SelectionMode being Single is important here as the approach for undoing a selection when multiple items are selected is more complicated.
Then, an object to represent our list items with the ToString() overload implemented so we don't have to fiddle with data templates and binding.
public class MyListViewItem
{
public string Text { get; set; }
public bool IsSelectable { get; set; }
public override string ToString()
{
return Text;
}
}
This object just represents a string (our data) paired with a boolean of whether or not we want this item to be selectable.
Then, a quick little setup to populate the list with a few item for testing:
public MainWindow()
{
InitializeComponent();
var items = new List<MyListViewItem>
{
new() { Text = "Item One", IsSelectable = true },
new() { Text = "Item Two", IsSelectable = true },
new() { Text = "Item Three", IsSelectable = false },
new() { Text = "Item Four", IsSelectable = true }
};
ItemsLv.ItemsSource = items;
}
And finally, the "magic". The SelectionChanged event handler:
private void ItemsLv_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// if this event wasn't caused by a new item that wasn't already
// selected being selected, don't do anything extra
if (e.AddedItems.Count <= 0)
{
return;
}
// get the newly selected item and cast it to a MyListViewItem so we can inspect the IsSelectable property
var selectedItem = (MyListViewItem)ItemsLv.SelectedItem;
// if it's a selectable item, we don't need to intervene
if (selectedItem.IsSelectable)
{
return;
}
// we made it this far that means we tried to select an item that should NOT be selectable
// if the new selected item caused us to UNselect an old item, put the selection back
if (e.RemovedItems.Count > 0)
{
ItemsLv.SelectedItem = e.RemovedItems[0];
}
// otherwise (the first selection ever?) just set selection back to null
else
{
ItemsLv.SelectedItem = null;
}
}
Hopefully the code comments in there make it clear what's going on.
I'm working on a WPF MVVM application and running into an issue. I'm familiar with WPF itself, however I've rarely used MVVM and I suspect I am doing something that MVVM doesn't support, however I don't know how else to accomplish what I am trying to do.
In the application, I have a user control called Agenda. It consist several controls including a text box, a button to add a new agenda item, and a list box with a custom template. The template includes an expander where the header is the agenda item title, up/down arrows to reorder items, and a button to delete the item. The expander content contains a toolbar and a rich text box. In the agenda UC I have a dependency property called ItemsSource which is an IEnumerable<AgendaItem>.
Now, I have a view called Appointment, its associated VM (AppointmentViewModel), and its model (AppointmentModel). In the model, there is a field called AgendaItems which is an ObservableCollection<AgendaItem>. The agenda UC is used within the appointment view and the UC's ItemsSource is bound to the Model.AgendaItems (the observable collection).
The problem I'm having is when I try to handle the buttons to reorder the agenda items in the UC. As an example, for the button to move an agenda item up the list, this is the code in the UC:
var tb = sender as Button;
var tag = tb.Tag as AgendaItem;
var lst = ItemsSource.ToList();
var index = lst.IndexOf(tag);
if(index > 0)
{
lst.RemoveAt(index);
lst.Insert(index - 1, tag);
ItemsSource = lst;
}
The tag of the up arrow is bound to the specific agenda item in the list so I know which item is being moved. The problem comes after I update the ItemsSource property doing ItemsSource = lst. After that line executes, the AgendaItems ObservableCollection in the VM is null. The binding mode is set to TwoWay.
Since the the appointment UC is used in various windows in the application, it made sense to me that the reordering of agenda items should be taken care of my the UC instead of duplicating code in every window which makes use of the UC. But updating the ItemsSource property in the UC results in the collection in the VM being null.
For reference, the ItemsSource property in the UC is defined as:
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<AgendaItem>), typeof(Agenda), new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
There is the regular .NET property:
public IEnumerable<AgendaItem> ItemsSource
{
get => (IEnumerable<AgendaItem>)GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
And the OnItemsSourceChanged method is:
private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is Agenda control)
{
if (e.OldValue is INotifyCollectionChanged oldValueINotifyCollectionChanged)
{
oldValueINotifyCollectionChanged.CollectionChanged -= control.ItemsSource_CollectionChanged;
}
if (e.NewValue is INotifyCollectionChanged newValueINotifyCollectionChanged)
{
newValueINotifyCollectionChanged.CollectionChanged += control.ItemsSource_CollectionChanged;
}
}
}
Any help/guidance on how I can reorder the ItemsSource collection in the UC without breaking the VM would be greatly appreciated. Thank you in advance.
Since calling .ToList() on Enumerable creates new list and reassigning it to ItemsSource doesn't work, maybe define the ItemsSourceProperty as IList<AgendaItem> since this is the interface that you need to reorder the items.
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IList<AgendaItem>), typeof(Agenda), new PropertyMetadata(null, new PropertyChangedCallback(OnItemsSourceChanged)));
(don't forget to change the signature of the ItemsSource property ).
You could also create list extension method to call it in your code like this:
ItemsSource.Move(ItemsSource.IndexOf(tag), MoveDirection.Up);
public static void Move<T>(this IList<T> list, int iIndexToMove,
MoveDirection direction)
{
if (list.Count > 0 && (direction == MoveDirection.Down && iIndexToMove < list.Count - 1)
|| (direction == MoveDirection.Up && iIndexToMove > 0))
{
if (direction == MoveDirection.Up)
{
var old = list[iIndexToMove - 1];
list[iIndexToMove - 1] = list[iIndexToMove];
list[iIndexToMove] = old;
}
else
{
var old = list[iIndexToMove + 1];
list[iIndexToMove + 1] = list[iIndexToMove];
list[iIndexToMove] = old;
}
}
}
public enum MoveDirection
{
Up,
Down
}
By changing the data type of ItemsSource from IEnumerable<AgendaItem> to ObservableCollection<AgendaItem> resolved the issue. Thank you to everyone who responded. It is much appreciated.
In ItemsControl I have placed my Collection of Items. As this collection can be very huge, I try to implement some paging - When scroll to the end of visible scroll area, add new items to ItemsControl.
Detecting of scroll bottom I've used from this answers How to find that ScrollViewer is scrolled to the end in WPF?
But I need to implement Adding of items.
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight &&
_productsViewModel.ProductTotal > pageCount*ConfigurationProvider.ItemsPerProductPage)
{
pageCount++;
_productsViewModel.RunPaginationWorker(pageCount);
}
Items are added in BG Worker. As parameter I send pageNumber which is amount of times when user scroll to the end of scroll.
In BG CompleteWork event, I receive items and try to bind them to Property which is binded to ItemsControl
var items= (e.Result) as List<ItemDto>;
Items.AddRange(items);
OnPropertyChanged("Items");
But this doesn't work, it seems like Property Items isn't changed.
public List<ItemDto> Items
{
get { return _items; }
set
{
_items= value;
OnPropertyChanged("Items");
}
}
And Xaml Binding
<ItemsControl VerticalAlignment="Top" MaxWidth="650" HorizontalAlignment="Center" Margin="10,40,10,0" Name="Items"
ItemsSource="{Binding Items}" AlternationCount="{Binding Path=Items.Count,FallbackValue='100'}">
...
</ItemsControl>
You should use ObservableCollection<T> instead of List<T> as it implements the interfaces:
INotifyCollectionChanged
INotifyPropertyChanged
As such it is very useful when you want to know when the collection has changed. An event is triggered that will tell the user what entries have been added/removed or moved.
If you want to have method AddRange(), then just copy this code:
private ObservableCollection<ItemDto> _items=new ObservableCollection<ItemDto>();
public ObservableCollection<ItemDto> Items
{
get { return _items; }
set
{
_items= value;
OnPropertyChanged("Items");
}
}
public void AddRange(IEnumerable<T> collection)
{
foreach (var i in collection)
{
Items.Add(i);
}
}
Update:
If you want just take the last items, I would suggest you linq methods. For example, you can skip 10 items and take the next 10 items:
Items=Items.Skip(10).Take(10).ToList();
Update 1:
When you adding new items to collection and you do not want draw new items, then you should just add VirtualizingStackPanel.VirtualizationMode="Recycling". This will reduce the numbers of times to re-render new controls.
What I'm trying to do :
I have 2 comboboxes, a "regular" one, and a filtrable one. On the filtrable combobox ItemsSource is binded to a property of the first combobox SelectedItem.
Here is some XAML to demonstrate the relation (I removed some properties):
<ComboBox Name="cbx_poche" ItemsSource="{Binding DataContext.ListePoches, ElementName=Main}" SelectedItem="{Binding PocheCible}" />
<controls:FilteredComboBox ItemsSource="{Binding SelectedItem.SupportsEligibles, ElementName=cbx_poche}" SelectedItem="{Binding Support}" />
The FilteredComboBox is a derived class from ComboBox inspired by those articles : Building a Filtered ComboBox for WPF / WPF auto-filtering combo.
The user can type in the combobox and it filters the list to display the items matching. The default behavior of the combobox is NOT desired (it automatically completes what the user types), this is why it's derived.
Those comboboxes above are in a ItemsControl element, because I need to have one row for each item in a specific collection. The SelectedItem properties of the comboboxes are binded to the item in this collection.
The result :
The problem :
It works quite well... as long as you don't select the same item in the first combobox (like in the example above : if I type some text that doesn't match the above combo, it will be reset).
As soon as several FilteredComboBox are linked to the same item in the first combobox (so binded to SelectedItem.SupportsEligibles), typing text in the FilteredComboBox filters both lists.
I know why it does that, I don't know how to fix it. So I tried two things :
Code 1 (current code) :
The problem is that the code uses the default view on the list, so all controls binded to this list will apply the same filter :
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
if (ItemsSourceView != null)
ItemsSourceView.Filter -= this.FilterPredicate;
ItemsSourceView = CollectionViewSource.GetDefaultView(newValue);
ItemsSourceView.Filter += this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
Code 2 :
So my (naïve) idea was to get a local view from the binded view. I works well for filtering, but breaks the binding (changing the selected item in the first combo doesn't update the list after the first pass)
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null && newValue != ItemsSourceView)
{
if (ItemsSourceView != null)
ItemsSourceView.Filter -= this.FilterPredicate;
ItemsCollectionViewSource = new CollectionViewSource { Source = newValue };
ItemsSourceView = ItemsCollectionViewSource.View;
ItemsSourceView.Filter += this.FilterPredicate;
this.ItemsSource = ItemsSourceView; // Breaks the binding !!
}
base.OnItemsSourceChanged(oldValue, newValue);
}
I'm stuck here.
I'm looking for some event or Binding class that I could use to be notified of the binding change, in order to update the view. Or maybe getting the view applied without having to change the ItemsSource
I ended up using a quite lame workaround, so I'm still interested in smart answers.
For people interested : I added another similar ItemsSource2 Dependency Property (still didn't find a nice name for it) on which I bind my item list, instead of the original ItemsSource.
When this items source is changed, the control gets a new CollectionView (not the default one) and sets it to the "standard" ItemsSource.
The other elements of the control remains identical (similar to the code in the linked articles).
public static readonly DependencyProperty ItemsSource2Property =
DependencyProperty.Register(
"ItemsSource2",
typeof(IEnumerable),
typeof(FilteredComboBox),
new UIPropertyMetadata((IEnumerable)null, new PropertyChangedCallback(OnItemsSource2Changed)));
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource2
{
get { return (IEnumerable)GetValue(ItemsSource2Property); }
set
{
if (value == null)
{
ClearValue(ItemsSource2Property);
}
else
{
SetValue(ItemsSource2Property, value);
}
}
}
private static void OnItemsSource2Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ic = (FilteredComboBox)d;
var oldValue = (IEnumerable)e.OldValue;
var newValue = (IEnumerable)e.NewValue;
if (newValue != null)
{
//Prevents the control to select the first item automatically
ic.IsSynchronizedWithCurrentItem = false;
var viewSource = new CollectionViewSource { Source = newValue };
ic.ItemsSource = viewSource.View;
}
else
{
ic.ItemsSource = null;
}
}
Is it possible to animate ListView items in C# ? The purpose is that I have a ListView that is asynchronously modified, and I'd like to animate smoothly the items that have been modified, until the user clicks them. This way items blinking, or whatever would do the trick, are the items that changed which haven't been reviewed yet.
Thank you guys !
UPDATE : sorry, i forgot. Never used WPF before, and I think it's too late to switch to it now. I'm using winforms.
I had to do something similar to what you are trying to do but instead of animating the ListView, I used custom checkbox layout in the list view to look different. The designer code for the ListView looks like:
this.listView1.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.None;
this.listView1.Location = new System.Drawing.Point(104, 90);
this.listView1.MultiSelect = false;
this.listView1.Name = "listView1";
this.listView1.Size = new System.Drawing.Size(264, 105);
this.listView1.Sorting = System.Windows.Forms.SortOrder.Ascending;
this.listView1.TabIndex = 7;
this.listView1.UseCompatibleStateImageBehavior = false;
this.listView1.View = System.Windows.Forms.View.Details;
this.listView1.MouseClick += new System.Windows.Forms.MouseEventHandler(this.listView1_MouseClick);
Then I declared a list that will hold the selection of the user.
private IList<ListViewItem> m_CheckedItems = new List<ListViewItem>();
Here is the initialise method for the ListView. You need to manipulate here for the initial look of your listview.
private void InitialiseListView(IList<string> data)
{
listView1.Items.Clear();
m_CheckedItems.Clear();
listView1.Columns.Clear();
listView1.Columns.Add("Col1");
listView1.Columns[0].Width = listView1.Width;
ListView.ListViewItemCollection collection = new ListView.ListViewItemCollection(listView1);
ImageList images = new ImageList();
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Unchecked);
images.Images.Add(global::MyApplication.Properties.Resources.Checkbox_Checked);
listView1.SmallImageList = images;
foreach (string str in data)
{
ListViewItem item = new ListViewItem();
item.ImageIndex = 0;
item.Text = str;
collection.Add(item);
}
}
This event triggers when the user selects an option in the list view. The selection is recorded in the list I created above and the checked image is displayed so that it looks like the user has selected the item.
private void listView1_MouseClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && listView1.FocusedItem != null)
{
if (listView1.FocusedItem.ImageIndex == 1)
{
listView1.FocusedItem.ImageIndex = 0;
m_CheckedItems.Remove(listView1.FocusedItem);
}
else
{
listView1.FocusedItem.ImageIndex = 1;
m_CheckedItems.Add(listView1.FocusedItem);
}
}
}
You can probably fiddle with fonts and forecolor of these items ... Each item within a List View is of type ListViewItem so you can individually manipulate it.
Hope this gives you some direction :)
You could use a MyListView: ListView and override OnDrawSubItem. Have the e.Item.Tag to store the "Clicked" state and update the background according to its state.
public partial class ObjectListView : ListView {
(....)
protected override void OnDrawSubItem(DrawListViewSubItemEventArgs e)
{
if(bool)e.Item.Tag)
(...) animate
}
You can do that easier in WPF.
WPF Basic
http://msdn.microsoft.com/en-us/library/ms754130.aspx
http://en.wikipedia.org/wiki/Windows_Presentation_Foundation
WPF Animation
http://msdn.microsoft.com/en-us/library/ms752312.aspx
ListView animation sample
(WPF) Animate ListView item move
WPF ListView animation by reorder of items?