ListView not updating on the itemsource ObservableCollection Item Property change - c#

Consider the following Sample Code :
View.xml
<Grid>
<ListView Name="NameList" HorizontalAlignment="Left" Height="142" Margin="55,45,0,0" VerticalAlignment="Top" Width="389">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label Content="{Binding FirstName}"/>
<Label Content="{Binding LastName}" Grid.Column="1"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Button" HorizontalAlignment="Left" Margin="120,256,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
View.xml.cs
public partial class MainWindow : Window
{
ViewModel vm;
public MainWindow()
{
InitializeComponent();
vm = new ViewModel();
this.DataContext = vm;
NameList.ItemsSource = vm.fn;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
vm.fn.Add(new fullname("P", "Q"));
vm.fn[0].FirstName = "NewName";
}
}
ViewModel.cs
class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
fn = new ObservableCollection<fullname>();
fn.CollectionChanged += ContentCollectionChanged;
fn.Add(new fullname("A", "B"));
fn.Add(new fullname("C", "D"));
}
public void ContentCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (fullname item in e.OldItems)
{
//Removed items
item.PropertyChanged -= EntityViewModelPropertyChanged;
}
}
else if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (fullname item in e.NewItems)
{
//Added items
item.PropertyChanged += EntityViewModelPropertyChanged;
}
}
}
public void EntityViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
//This will get called when the property of an object inside the collection changes
}
public ObservableCollection<fullname> _fn;
public ObservableCollection<fullname> fn
{
get { return _fn; }
set { _fn = value; OnPropertyChanged("fn"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
}
}
}
Model.cs
class fullname : INotifyPropertyChanged
{
public fullname(string f, string l)
{
FirstName = f;
LastName = l;
}
public string _FirstName;
public string FirstName
{
get { return _FirstName; }
set { _FirstName = value; OnPropertyChanged("FirstName"); }
}
public string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value; OnPropertyChanged("LastName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
}
}
}
If I add or remove any items in the ObservableCollection it updates the ListView in the View correctly but the problem is that if i modify the ObservableCollection item property the ListView is not updated.
for ex:
On click of above specified button it
a. Adds a new item (Successfully reflected in the ListView)
b. Modify FirstName of first item (Not reflected in the ListView)
What should i do so as to make the modifications reflect in View.
I would be very thankful if anyone could point out what i am doing wrong.

ObservableCollection event are not fired when an item is changed, only when it is added, removed or moved.
You must implement INotifyPropertyChanged interface in item class, and register each item event in collection.
See here
If you make heavy changes in the collection, you can raise a
NotifyCollectionChangedAction.Reset
event to reset them all
See here

I was also struggling with the same issue on WinUI with ListView and ObservableCollection.
What you need to understand is that ObservableCollection is only responsible to notify collection changes. Item level change are handle through INotifyPropertyChanged. However for those to be applied you need to make sure your binding mode is at least one way, two way if you need to.
Your XAML should look like that:
Text="{x:Bind Title, Mode=OneWay}
Do not attempt what's proposed there, it's just wrong:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)

Related

Revit API WPF C#: Check All button for Check Boxes in Listbox

I'm pretty new to programming with WPF and C# and I have a question regarding the possibility to automatically check all the CheckBoxes in a Listbox. I'm developing a plugin for Autodesk Revit and, after having listed all the names of the rooms in a list box, I want to check them all using the button "Check All"
I've read the thread at this page but still, I'm not able to make it work. May someone help me with my code?
Here is what I've done:
XAML:
<ListBox x:Name='roomlist'
SelectionMode='Multiple'>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked='{Binding IsChecked}'
Content="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.InputBindings>
<KeyBinding Command="ApplicationCommands.SelectAll"
Modifiers="Ctrl"
Key="A" />
</ListBox.InputBindings>
<ListBox.CommandBindings>
<CommandBinding Command="ApplicationCommands.SelectAll" />
</ListBox.CommandBindings>
</ListBox>
C#
public partial class RoomsDistance_Form : Window
{
UIDocument _uidoc;
Document _doc;
public RoomsDistance_Form(Document doc, UIDocument uidoc)
{
InitializeComponent();
FilteredElementCollector collector = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.OfCategory(BuiltInCategory.OST_Rooms);
List<String> myRooms = new List<String>();
foreach (var c in collector)
{
myRooms.Add(c.Name);
}
myRooms.Sort();
roomlist.ItemsSource = myRooms;
}
private void checkAllBtn_Click(object sender, RoutedEventArgs e)
{
foreach (CheckBox item in roomlist.Items.OfType<CheckBox>())
{
item.IsChecked = true;
}
}
public class Authority : INotifyPropertyChanged
{
private bool isChecked;
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Thank you very much for your help!
In the thread you are linking to, they are setting the "IsChecked" on the data object (Authority), not the CheckBox control itself.
foreach (var a in authorityList)
{
a.IsChecked = true;
}
You have a binding to IsChecked that will update the Checkbox control when NotifyPropertyChanged() is called.
After having lost my mind in the effort i solved my problem by avoiding the Listbox.. I simply added single CheckBoxes in the StackPanel.
XAML:
<ScrollViewer Margin='10,45,10,100'
BorderThickness='1'>
<StackPanel x:Name='stack'
Grid.Column='0'></StackPanel>
</ScrollViewer>
C#:
foreach (var x in myRooms)
{
CheckBox chk = new CheckBox();
chk.Content = x;
stack.Children.Add(chk);
}
Not what i was looking for but now it works and that's the point.
Thank you for your help!
I usually use CheckBoxList in the following way:
In xaml:
<ListBox ItemsSource="{Binding ListBoxItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"> //+some dimensional properties
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In xaml.cs:
public partial class MyWindow : Window
{
public ViewModel ViewModel {get; set; }
public MyWindow(ViewModel viewModel)
{
//keep all the mess in ViewModel, this way your xaml.cs will not end up with 1k lines
ViewModel = viewModel;
DataContext = ViewModel;
InitializeComponent();
}
void BtnClick_SelectAll(object sender, RoutedEventArgs e)
{
ViewModel.CheckAll();
}
}
ViewModel preparation:
public class ViewModel
{
public List<ListBoxItem> ListBoxItems { get; set; }
//InitializeViewModel()...
//UpdateViewModel()...
//other things....
public void CheckAll()
{
foreach (var item in ListBoxItems)
{
item.IsSelected = true;
}
}
public class ListBoxItem : INotifyPropertyChanged
{
public string Name { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (_isSelected != value)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

How to update visibility at runtime in WPF

I am currently developing a hamburger style menu in WPF. In this menu, there are some categories that each have an icon. When the menu is collapsed you can still see those icons. When you expand the menu, there should appear text next to it. My idea was to just set their visibility to Visible as soon as the menu opens but I've had a lot of trouble realizing this. Right now I'm trying to change their visibility by binding them to a property.
XAML:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
C# CS Class:
using System.Windows;
using System.Windows.Controls;
namespace APP
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool menuOpen = false;
private int closedMenuWidth = 50;
private int openMenuWidth = 210;
private string textboxVisibility;
public string TextboxVisibility
{
get { return textboxVisibility; }
set { textboxVisibility = value; }
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.TextboxVisibility = "Hidden";
}
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
if (menuOpen)
{
menuGrid.Width = closedMenuWidth;
menuOpen = false;
this.TextboxVisibility = "Hidden";
}
else
{
menuGrid.Width = openMenuWidth;
menuOpen = true;
this.TextboxVisibility = "Visible";
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
}
}
When I change the value within the MainWindow function, it does have an effect on it when it first starts. But the other times I try to change it, which is at runtime, nothing happens. I have tried all sorts of things with booleans and binding the actual Visibility type but nothing worked.
You should implemente INotifyPropertyChanged on your MainWindow class like this:
public partial class MainWindow: Window,INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What OnPropertyChanged method does is, whenever the value is setted, it notifies the view and refreshes it.
This will solve the problem but isn't the right way to use MVVM.
The way you should do this is to change the visibility property of the TextBox instead of binding the visibility property to a value:
First you have to add a name to the TextBlock you want to hide:
<ListView x:Name="menuItemsListView" ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Image x:Uid="Test" Name="InhoudImage" Source="Images/noimage.png" Height="30" Width="auto" VerticalAlignment="Center" Margin="3,0,0,0"></Image>
<TextBlock Name="textblock" x:Uid="Test" Text="{Binding Path=TextboxVisibility}" Visibility="{Binding Path=TextboxVisibility}" VerticalAlignment="Center"></TextBlock>
</StackPanel>
</ListViewItem>
</ListView>
And then you change the visibility in the code
private void MenuButton_Click(object sender, RoutedEventArgs e) {
if (menuOpen) {
menuGrid.Width = closedMenuWidth;
menuOpen = false;
textblock.Visibility = System.Windows.Visibility.Hidden;
}
else {
menuGrid.Width = openMenuWidth;
menuOpen = true;
textblock.Visibility = System.Windows.Visibility.Visible;
//foreach (ListViewItem item in menuItemsListView.Items)
//{
// item.
// if (item.Uid == "Test")
// {
// item.Visibility = Visibility.Visible;
// }
//}
}
}
If you want to implement MVVM the right way you have to create a ViewModel class and add it as Data Context to your view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
And then on you MainWindowViewModel is where you change the property:
public class MainWindowViewModel: INotifyPropertyChanged {
private string textboxVisibility;
public string TextboxVisibility {
get {
return textboxVisibility;
}
set {
textboxVisibility = value;
OnPropertyChanged();
}
}
//The rest of your code goes here
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) {
PropertyChanged ? .Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}

Prevent Bound ComboBox from firing SelectionChanged Event at application startup

I have a ComboBox bound to a ViewModel Items Source List.
The ComboBox also has a SelectionChanged Event.
I want the event to fire only when the user has clicked the ComboBox and selected a new item.
But the event fires at application startup when the bound items are loaded and the default item is programmatically selected.
XAML
<ComboBox x:Name="cboDisplay"
ItemsSource="{Binding Display_Items}"
SelectedValue="{Binding Display_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="10,10,0,0"
Height="22"
Width="100"
SelectionChanged="cboDisplay_SelectionChanged" />
C#
private void cboDisplay_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Event Fired");
}
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private void OnPropertyChanged(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
public MainViewModel()
{
// Default Item at Startup
Display_SelectedItem = "Windowed";
}
// Items Source
//
private List<string> _Display_Items = new List<string>()
{
"Fullscreen",
"Windowed"
};
public List<string> Display_Items
{
get { return _Display_Items; }
set
{
_Display_Items = value;
OnPropertyChanged("Display_Items");
}
}
// Selected Item
//
private string _Display_SelectedItem { get; set; }
public string Display_SelectedItem
{
get { return _Display_SelectedItem; }
set
{
if (_Display_SelectedItem == value)
{
return;
}
_Display_SelectedItem = value;
OnPropertyChanged("Display_SelectedItem");
}
}

creating dynamic controls with MVVM

I am trying to create a dynamic control with using mvvm for the first time. I want to generate buttons dynamically and have the content display inside of the buttons. I am sure I am missing something really easy here, but I have no idea what it could be. When I run the code, nothing appears on the interface, though I can see AvailableMonitorOC populate in the constructor...
Here is my ViewModel where I manually add buttons to an observable collection for simplicity sake of this example:
public class CreateAndDisplayViewModel {
public ObservableCollection<AvailableMonitorBo> AvailableMonitorOC = new ObservableCollection<AvailableMonitorBo>();
public CreateAndDisplayViewModel() {
availableMonitorBo = new AvailableMonitorBo();
availableMonitorBo.AvailableMonitorLabel = "Label 1";
AvailableMonitorOC.Add(availableMonitorBo);
availableMonitorBo.AvailableMonitorLabel = "Label 2";
AvailableMonitorOC.Add(availableMonitorBo);
}
private AvailableMonitorBo availableMonitorBo;
public AvailableMonitorBo AvailableMonitorBo {
get { return availableMonitorBo; }
set {
availableMonitorBo = value;
}
}
}
Here is my model:
public class AvailableMonitorBo : INotifyPropertyChanged {
private string availableMonitorLabel { get; set; }
public string AvailableMonitorLabel {
get { return availableMonitorLabel; }
set {
availableMonitorLabel = value;
OnPropertyChanged("AvailableMonitorLabel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And here is the xaml:
<ListView Grid.Row="2" Grid.Column="1"
ItemsSource="{Binding AvailableMonitorOC, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Button Content="{Binding AvailableMonitorLabel}"
Width="100"
Height="25"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The main reason for you lack of display is that AvailableMonitorOC needs to be a property of CreateAndDisplayViewModel, not a field as it is currently.
You're also only creating one AvailableMonitorBo instance and changing its caption each time.

Observable collection not reacting to it's collecion

My ListBox doesn't react to my ObservableCollection.
This is the ListBox I am talking about.
<ListBox x:Name="CreateFieldsList"
HorizontalAlignment="Left"
Height="218"
VerticalAlignment="Top"
Width="244"
Margin="0,86,0,0"
BorderBrush="#FFB9B9B9">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="4"
Width="215"
Height="32.96"
Background="White">
<TextBlock Text="{Binding Name}"
FontWeight="Normal"
FontSize="18.667"
Padding="8,3,0,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
In my MainWindow, I prepare the data binding like this
private ObservableCollection<FieldListItem> _fieldItems;
public MainWindow()
{
InitializeComponent();
_fieldItems = new ObservableCollection<FieldListItem>();
CreateFieldsList.ItemSource = _fieldItems;
}
A FieldListItem is following
public class FieldListItem : ViewItem
{
private Field _field;
public string Name
{
get { return _field.Name; }
}
public string Value
{
get { return _field.Value; }
}
public FieldListItem(Field f)
{
_field = f;
}
}
and finally the ViewItem
public class ViewItem : INotifyPropertyChanged
{
private event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string caller = "")
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
//The interface forces me to implement this. Why?
event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
{
add { }
remove { }
}
}
I don't know why this isn't working. Could you please help?
The INotifyPropertyChanged interface needs you to implement an event. Your event implementation does not work because registrations and deregistrations are ignored because the add and remove blocks are empty.
Implement the event without add and remove:
public class ViewItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string caller = "")
{
var copy = PropertyChanged;
if (copy != null)
copy(this, new PropertyChangedEventArgs(caller));
}
}

Categories