WPF C# MVVM ListView not updating - c#

I see similar questions regarding ListView not updating in MVVM, however I have been struggling for quite a while already..
I have 2 classes, 1 of which is part of the other, such as:
public class Info
{
public string Name { get; set; }
public string Status { get; set; }
....
public ObservableCollection<Content> UserContent { get; set; } = new ObservableCollection<Content>();
}
public class Content
{
public string Filename { get; set; }
public string Path { get; set; }
public string Type { get; set; }
}
The page is divided into 2 columns, left is itemscontrol and right is a single usercontrol. When an itemscontrol is clicked, it passes the datacontext to the usercontrol
<local:ScreenControl DataContext="{Binding MainPageViewModel.SelectedDevice,
Source={x:Static local:ViewModelLocator.Instance}}" />
UserControl contains a TabControl. One of the tab display details from the Info class
.....
<TextBlock Style="{StaticResource localTextBlock}" Text="{Binding Name}" />
<TextBlock Style="{StaticResource localTextBlock}" Text="{Binding Location}" />
.....
And up to here, all is good.
Problem starts on the other tab. I have a button that will OpenFileDialog, browse to video file and add the file to the UserContent. Then the listview should display.
public ICommand AddVidCommand { get; set; }
AddVidCommand = new RelayCommand(AddVid);
public void AddVid()
{
if (MainPageViewModel.SelectedDevice is ScreenInfo info)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "Video Files|*.mp4;*.mkv;*.wmv";
if (openFileDialog.ShowDialog() == true)
{
info.UserContent.Add(new Content
{
Filename = openFileDialog.SafeFileName,
Path = openFileDialog.FileName
});
}
}
}
<TabItem Header="Playlist">
<StackPanel Orientation="Vertical" >
<Button Content="Add"
Command="{Binding ScreenControlViewModel.AddVidCommand,
Source={x:Static local:ViewModelLocator.Instance}}"
CommandParameter="{Binding}"/>
<ListView x:Name="fileList"
ItemsSource="{Binding UserContent}">
<ListView.View>
<GridView>
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Type}" Header="Type" />
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Filename}" Header="Name" />
<GridViewColumn Width="100" DisplayMemberBinding="{Binding Path}" Header="Path" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</TabItem>
Adding MainPageViewModel
public class MainPageViewModel : BaseViewModel
{
public static ObservableCollection<ScreenInfo> Devices { get; set; }
public static ScreenInfo SelectedDevice { get; set; }
public MainPageViewModel()
{
Devices = new ObservableCollection<ScreenInfo>();
}
}
As you probably guessed it, my listview does not update. I can clearly see the info being passed to the class in visual studio via the button command, but the UI doesn't show..

Answer provided by Clemens in a comment on the question:
You are mixing static and non-static members in your classes. Each time an instance of MainPageViewModel is created, the static Devices property value is replaced with a new ObservableCollection, without notifying consumers of this property. Don't use static properties with MVVM.

Related

Binding a value to a Radiobutton and Checkbox in a ListView

I have a ListView which have a column containing a radiobuttons and a column containing checkboxes. What I would like to do is bind this radiobutton to a value based on a int value on a property.
My ListView look like this:
<ListView x:Name="lvSalesmen" ItemsSource="{Binding}" Margin="311,32,0,244" Grid.ColumnSpan="5">
<ListView.View>
<GridView>
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Path=Id}"/>
<GridViewColumn Header="First Name" DisplayMemberBinding="{Binding Path=FirstName}"/>
<GridViewColumn Header="Last Name" DisplayMemberBinding="{Binding Path=LastName}"/>
<GridViewColumn Header="IsResponsible">
<GridViewColumn.CellTemplate>
<DataTemplate>
<RadioButton GroupName="IsResponsible" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="IsSecondary">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
and this is where I set my DataContext in the code-behind file:
private void Vm_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
lvSalesmen.DataContext = vm.Salesmen;
}
My ViewModel look like this:
public class DistrictsListViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private List<Salesman> _salesman;
private ISalesmenService _salesmenService = new SalesmenService();
public DistrictsListViewModel()
{
}
public async void GetAllSalesmenWithResponsibilityByDistrictId( int id)
{
Salesmen = await _salesmenService.GetSalesmanReponsibilityByDistrictIdAsync(id);
}
public List<Salesman> Salesmen {
get { return _salesman; }
set {
_salesman = value;
PropertyChanged(this, new PropertyChangedEventArgs("Salesmen"));
}
}
Salesmen is a List where the Salesman object looks like this:
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int IsResponsible { get; set; }
public int IsSecondary { get; set; }
What I would like to achieve is to check the radiobutton in the row where the 'IsResponsible'-property value equals 1. Only one radiobutton can be checked at a time. This I have achieved by adding the 'GroupName'.
Furthermore, the checkbox should be checked in the 'IsSecondary'-column, if the IsSecondary-property value equals 1. Multiple checkboxes can be checked at the same time.
You have a general type mismatch problem. IsChecked is generally a bool value but there are only int values available.
There are two ways to work this out:
You could change the type of IsResponsible and IsSecondary to bool in GetSalesmanReponsibilityByDistrictIdAsync
You can add another property to bind to which internally converty the value of IsResponsible and IsSecondary to bool in the Getoperation
With either one of these suggestions you can then bind the given value to IsChecked in you WPF like:
<RadioButton IsChecked="{Binding IsResponsibleBool, Mode=OneWay}" GroupName="IsResponsible" />
<CheckBox IsChecked="{Binding IsResponsibleBool, Mode=OneWay}"/>
Possbile solution for suggestion #2:
public Boolean IsResponsibleBool
{
get => this.IsResponsbile == 1;
}
public Boolean IsSecondaryBool
{
get => this.IsSecondary == 1;
}

How to look for specific ListViewItem in a ListView - C# (WPF)

I have created ListView (C#-WPF) with three columns: Number, Action and File. First of all I'm adding several different Items to the ListView, the 'Number' is increasing with each entry added, the 'Action' is recording what exactly happened (Moved/Renamed/Removed) and the 'File' column displays for which file an action occurred.
XAML:
<ListView x:Name="ActionFile" HorizontalAlignment="center" Height="100" VerticalAlignment="bottom" Width="780" Margin="20,0,0,0">
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
C#:
public class FileActionEntry
{
public int NumberX { get; set; }
public string ActionX { get; set; }
public string FileX { get; set; }
}
ActionFile.Items.Add(new FileActionEntry() { NumberX = numValue, ActionX = actionValue, FileX = fileValue });
Now, I was trying to create a foreach loop that would check whether a specific action was taken for a specific file, then to clear that entry from a ListView and return its 'Number' value. I had several different approaches but I couldn't find out how to extract a column values out of an item. I thought it could be done by using '.SubItems' but it seems that it does not work for a WPF.
The best way of doing so is to consider binding your listView ItemSource to an ObservableCollection, implement the INotifyPropertyChanged Interface, and any update to that collection will be automatically reflected on the UI.
Consider the following example based on yours, where the entry which the fileName is provided in the TextBox is removed from the collection:
Xaml
<Grid>
<Grid VerticalAlignment="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Content="Process" Click="ButtonBase_OnClick"/>
<TextBox Grid.Column="1" Margin="2" Text="{Binding SearchText, Mode=TwoWay}"/>
</Grid>
<ListView x:Name="ActionFile" HorizontalAlignment="center" Height="100" VerticalAlignment="bottom" Width="780" Margin="20,0,0,0" ItemsSource="{Binding FileActionEntryCollection}">
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>
Code behind
public class FileActionEntry
{
public int NumberX { get; set; }
public string ActionX { get; set; }
public string FileX { get; set; }
}
public partial class MainWindow : Window, INotifyPropertyChanged
{
private string _searchText = "";
public string SearchText
{
get
{
return _searchText;
}
set
{
if (_searchText == value)
{
return;
}
_searchText = value;
OnPropertyChanged();
}
}
private ObservableCollection<FileActionEntry> _fileActionEntryCollection = new ObservableCollection<FileActionEntry>()
{
new FileActionEntry(){ ActionX = "Moved", FileX = "File1", NumberX = 1},
new FileActionEntry(){ ActionX = "Renamed", FileX = "File2", NumberX = 2},
new FileActionEntry(){ ActionX = "Removed", FileX = "File3", NumberX = 3}
};
public ObservableCollection<FileActionEntry> FileActionEntryCollection
{
get
{
return _fileActionEntryCollection;
}
set
{
if (_fileActionEntryCollection == value)
{
return;
}
_fileActionEntryCollection = value;
OnPropertyChanged();
}
}
public MainWindow()
{
InitializeComponent();
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (FileActionEntryCollection.Any(f => f.FileX == SearchText))
FileActionEntryCollection.Remove(FileActionEntryCollection.First(f => f.FileX == SearchText));
}
}
You should (must) use data binding (Data Binding Overview in WPF). This will make your life a lot easier. An essential class in the context of data binding is the ObservableColllection. This collection will notify the binding target about any changes (e.g., add or remove). All ItemsControl like the ListView listen to this changes and will update their view to reflect those changes. So you never need to access the control directly to add or remove items.
The view model is the binding source:
class ViewModel : INotifyPropertyChanged
{
// Ctor
public ViewModel() => this.FileActionEntries = new ObservableCollection<FileActionEntry>();
private void AddFileActionToListView()
{
var newFileEntry = new FileActionEntry() { NumberX = numValue, ActionX = actionValue, FileX = fileValue };
// Add an item to the ListView
// or any other control that binds to FileActionEntries (ObservableCollection)
this.FileActionEntries.Add(newFileEntry);
}
private int CheckFileAction(string action, string file)
{
// Throws an exception if file not found
FileActionEntry fileEntry = this.FileActionEntries.First(entry => entry.FileX.Equals(file, StringComparison.OrdinalIgnoreCase));
// TODO: Check action
// Remove an item from the ListView
// or any other control that binds to FileActionEntries (ObservableCollection)
this.FileActionEntries.Remove(fileEntry);
return fileEntry.NumberX;
}
private ObservableCollection<FileActionEntry> fileActionEntries;
public ObservableCollection<FileActionEntry> FileActionEntries
{
get => this.fileActionEntries;
set
{
this.fileActionEntries = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML with a bound ItemsSource:
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListView x:Name="ActionFile"
ItemsSource="{Binding FileActionEntries}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Number" Width="40" DisplayMemberBinding="{Binding NumberX}"/>
<GridViewColumn Header="Action" Width="200" DisplayMemberBinding="{Binding ActionX}"/>
<GridViewColumn Header="File" Width="350" DisplayMemberBinding="{Binding FileX}"/>
</GridView>
</ListView.View>
</ListView>
</Window>

Binding ListView inside combobox item not binding(showing)

I'm trying to create a Combobox with multiple items and header and way I'm approaching is to create a listView (or DataGrid) inside the Combobox
but for some reason, the items won't bind
as you can see no items in the list
XAML (when ComboBox.ItemTemplate have used the items showed properly with no headers of course)
<ComboBox
materialDesign:HintAssist.Hint="בחר מתכון מהרשימה">
<!--ItemsSource = "{Binding Path=Recipes}" >-->
<!--DisplayMemberPath = "Description">-->
<ListView ItemsSource="{Binding Recipes}"
SelectedItem="{Binding Path=SelectedRecipe}"
Height="200" ScrollViewer.VerticalScrollBarVisibility="Visible" IsEnabled="False" Focusable="False">
<ListView.View>
<GridView>
<GridViewColumn Width="130" Header="Description" DisplayMemberBinding="{Binding Description}" />
</GridView>
</ListView.View>
</ListView>
</ComboBox>
ViewModel (im using Prism as my MVVM library)
public ObservableCollection<Recipes> Recipes
{
get { return _recipes; }
set { SetProperty(ref _recipes, value); }
}
private ObservableCollection<Recipes> _recipes = new ObservableCollection<Recipes>();
private async void FillRecipesList() //this is call on program startup
{
if (Recipes != null && Recipes.Count > 0)
{
Recipes.Clear();
}
var result = await _mSql.GetRecipes();
if (result.Count() > 0)
Recipes.AddRange(result);
}
Model
public class Recipes
{
public long AutoNum { get; set; }
public int? RecipeCode { get; set; }
public int? RecipeVersion { get; set; }
public string Description { get; set; }
}
You should try to call the method
FillRecipesList()
in your ViewModel's constructor

Get selected Value in ListView c#

I just want to get the selected item in my ListView.
This the XAML:
<TabItem Header="Musique" Background="#1874CD" BorderBrush="#68838B">
<ListView x:Name="ListM" Width="Auto" Background="#D1EEEE" ItemsSource="{Binding Path=MediaCollection}" Margin="-8,-0.877,-1,-2.925" SelectionChanged="ListM_SelectionChanged">
<ListView.View>
<GridView AllowsColumnReorder="True" >
<GridViewColumn Header="" Width="Auto" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Image Name="IconImage" Source="{Binding IconUri}" Panel.ZIndex="2" Width="15" Height="15"></Image>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Title}" Header="Titre" Width="auto" />
<GridViewColumn DisplayMemberBinding="{Binding Composer}" Header="Artiste" Width="70" />
<GridViewColumn DisplayMemberBinding="{Binding Length}" Header="Durée" Width="50" />
<GridViewColumn DisplayMemberBinding="{Binding Album}" Header="Album" Width="70" />
</GridView>
</ListView.View>
</ListView>
I insert item with this piece of code :
ListM.Items.Add(new ListGrid() { IconUri = imagemp3.Source, Title = Ftitle, Length = duration, Album = Falbum, Composer = Fcomposer });
I need to get these details (Title, Length, ...) when I select an item on my list. I've tried but had many issues and it still doesn't work.
Using SelectedItems :
SelectedItems is the set of selected rows,
If you want to get only the Title:
string age = ListM.SelectedItems[0].SubItems[0].Text;
If you want to get all the details :
ListM.SelectedItems[0] returns an object. You first need to cast it to its specific type before you can access its members.
Create a Media class (Title, Composer...), then :
private void getSelectedIteminListView(object sender, MouseButtonEventArgs e)
{
Media media = (Media)ListM.SelectedItems[0];
}
Don't forget to add the event :
ListM.DoubleClick += getSelectedIteminListView;
Just cast the SelectedItem to your type:
private void ListM_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listgrid = (ListGrid)ListM.SelectedItem;
}
But it seems you are mixing something. You are using DataBinding (ItemsSource) but you also manually add items.
Better you only use DataBinding by adding the items to MediaCollection in your ViewModel and bind the SelectedItem to another property in the ViewModel:
in XAML:
SelectedItem="{Binding SelectedItem}"
I finaly listen to #M.REJEB
private void ListM_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListGrid media = (ListGrid)ListM.SelectedItems[0];
string age = media.Title;
MessageBox.Show("Selected : " + age);
}
And it works with this class :
class ListGrid
{
public string Path { get; set; }
public string Title { get; set; }
public System.TimeSpan Length { get; set; }
public string Album { get; set; }
public string Composer { get; set; }
public System.Windows.Media.ImageSource IconUri { get; set; }
}

Bind ObservableCollection to ListView

I am having some trouble to bind my collection to my listview. I have tried a lot of different approaches from others on here for binding, and also followed this tutorial at first. I made it to work but it was not the way I wanted.
Anymway, here is the XML:
<ListView Grid.Row="0" ItemsSource="{Binding SongList}" SelectionMode="Extended" x:Name="ListViewMain" VerticalAlignment="Top" ScrollViewer.VerticalScrollBarVisibility="Visible" Margin="0,1,0,0" Height="264" >
<ListView.View>
<GridView>
<GridViewColumn Header="Title" DisplayMemberBinding="{Binding Title}" Width="500"/>
<GridViewColumn Header="Artist" DisplayMemberBinding="{Binding Artist}" Width="100"/>
<GridViewColumn Header="Album" DisplayMemberBinding="{Binding Album}" Width="100"/>
<GridViewColumn Header="Length" DisplayMemberBinding="{Binding Length}" Width="100"/>
<GridViewColumn Header="Location" DisplayMemberBinding="{Binding Songfile}" Width="100"/>
</GridView>
</ListView.View>
</ListView>
In my code, to add items to my collection, I do :
public MainWindow()
{
InitializeComponent();
...
PlayListItem addsong = new PlayListItem(title, artist, album, length, filename);
}
The PlayListItem class with the ObservableCollection :
public class PlayListItem
{
public ObservableCollection<Song> _SongList = new ObservableCollection<Song>();
public ObservableCollection<Song> SongList { get { return _SongList; } }
public PlayListItem(string _Title, string _Artist, string _Album, string _Length, string _Filename)
{
_SongList.Add(new Song
{
Title = _Title,
Artist = _Artist,
Album = _Album,
Length = _Length,
SongFile = _Filename,
});
}
public class Song
{
public string Artist { get; set; }
public string Album { get; set; }
public String Title { get; set; }
public string Length { get; set; }
public String SongFile { get; set; }
}
}
I think my items are added correctly each time I call the constructor, but it does not get updated on the ListView. I do not have some errors about the binding in the Output window either.
Any ideas and help would be appreciated.
EDIT :
By adding :
ListViewMain.ItemsSource = addsong.SongList;
Right after creating a new PlayListItem seems to solve the problem, as the ListView is now printing the item.
Two problems. 1) I don't see where a DataContext is set. You can do that at the window level or individual control level. 2). PlayListItem does not implement INotifyPropertyChanged. If you set SongList after InitializeComponent, INotifyPropertyChanged is needed, but not if it's before.
Did you try using a two-way binding? A one way binding will not update the other side. Add this: Mode="TwoWay" in the itemsource binding.

Categories