Binding a Listview to an ObservableCollection Object (from XAML) [duplicate] - c#

This question already has answers here:
Why does WPF support binding to properties of an object, but not fields?
(2 answers)
Closed last year.
This is the code behind, an array of People (ObservableCollection ) objects.
private ObservableCollection<People> data = new ObservableCollection<People>();
public SecondWindow()
{
InitializeComponent();
data.Add(new People() { Name = "JOhn Doe", Age="34" });
data.Add(new People() { Name = "Jane Doe", Age = "45" });
data.Add(new People() { Name = "Peter Singh", Age = "26" });
this.DataContext = this;
}
public class People : INotifyPropertyChanged
{
private String name, age;
public String Name
{
get { return this.name; }
set
{
if (this.name != value)
{
this.name = value;
this.NotifyPropertyChanged("Name");
}
}
}
public String Age
{
get { return this.age; }
set
{
if (this.age != value)
{
this.age = value;
this.NotifyPropertyChanged("Age");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String prop)
{
if (this.PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
This is the XAML code, but it's not binding
<BlockUIContainer>
<ListView BorderThickness="0"
ItemsSource="{Binding Path=data}">
<ListView.View>
<GridView>
<GridViewColumn
Header="Name"
DisplayMemberBinding="{Binding Name}"
Width="150" />
<GridViewColumn
Header="Age"
DisplayMemberBinding="{Binding Age}"
Width="75" />
</GridView>
</ListView.View>
</ListView>
</BlockUIContainer>
Any ideas?

The problem comes from using a field and not a public property for data:
public ObservableCollection<People> data { get; } = new ObservableCollection<People>();

Related

WPF Observable Collection not updating

i'm quite the rookie in the WPF enviroment
i have been scouring for a solution, although i'm sure it's just something very basic i have yet to understand
I'm trying to make use of Observable collection to update a Listview
I have added a method in the viewmodel, i need to call from outside code to add another item to the list.
When i call method addTask in the ViewModel with debugger on, i can see it counts up 1 item in the list. But it doesn't add it to the ListView
Model:
public class Tasks : INotifyPropertyChanged
{
private string taskName;
private string fromTime;
private string toTime;
private string message;
private string taskCreator;
public string TaskName
{
get
{
return taskName;
}
set
{
taskName = value;
OnPropertyChanged("TaskName");
}
}
public string FromTime
{
get
{
return fromTime;
}
set
{
fromTime = value;
OnPropertyChanged("FromTime");
}
}
public string ToTime
{
get
{
return toTime;
}
set
{
toTime = value;
OnPropertyChanged("ToTime");
}
}
public string Message
{
get
{
return message;
}
set
{
message = value;
OnPropertyChanged("Message");
}
}
public string TaskCreator
{
get
{
return taskCreator;
}
set
{
taskCreator = value;
OnPropertyChanged("TaskCreator");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
the ViewModel:
class TasksViewModel
{
public TasksViewModel()
{
{
_UsersList.Add(new Tasks() { TaskName = "TaskName1", FromTime = "03:00", ToTime = "07:00", TaskCreator = "TaskCreator1", Message = "Hello" });
_UsersList.Add(new Tasks() { TaskName = "TaskName2", FromTime = "03:00", ToTime = "07:00", TaskCreator = "TaskCreator2", Message = "Hello" });
_UsersList.Add(new Tasks() { TaskName = "TaskName3", FromTime = "03:00", ToTime = "07:00", TaskCreator = "TaskCreator3", Message = "Hello" });
};
}
public ObservableCollection<Tasks> Tasks
{
get { return _UsersList; }
}
public ObservableCollection<Tasks> _UsersList { get; set; } = new ObservableCollection<Tasks>();
public void addTask(string taskName, string fromTime, string toTime, string taskCreator, string message)
{
_UsersList.Add(new Tasks() { TaskName = taskName, FromTime = fromTime, ToTime = toTime, TaskCreator = taskCreator, Message = message });
}
The list view i want to update (Xaml)
<ListView Name="TaskGrid1" Grid.Row="1" Grid.Column="1" Margin="4,4,12,13" ItemsSource="{Binding Tasks}" RenderTransformOrigin="0.5,0.5" FontSize="30" >
<ListView.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</ListView.RenderTransform>
<ListView.View>
<GridView x:Name="List00000600">
<GridViewColumn Header="Tid" DisplayMemberBinding="{Binding FromTime}" Width="100"/>
<GridViewColumn Header="Opgave" DisplayMemberBinding="{Binding TaskName}" Width="350" />
<GridViewColumn Header="Opretter" DisplayMemberBinding="{Binding TaskCreator}" Width="120" />
</GridView>
</ListView.View>
</ListView>
I have no idea how you've assigned viewmodel in app.xaml.
Just open the xaml file, which holds your listview and build your window as usual:
<Window
... (rest of xmlns)
xmlns:MyViewModels="clr-namespace:YourViewModelNamespace"
>
<Window.DataContext>
<MyViewModels:TasksViewModel/>
</Window.DataContext>
<Grid/Or any container>
...
<ListView... />
</Grid/Or any container
</Window>
As mentioned, replace _UserList with Tasks.
Your async TasksCreate() is creating new instance of TasksViewModel so it will never update current one.
PS: you can obtain viewmodel by:
// this function belongs to mainwindow/anywindow
public void CodeBehindClickEvent(object sender, RoutedEventArgs e)
{
var VM = (TasksViewModel)this.DataContext;
VM.addTask("blabla", ...)
VM.TasksCreate();
}

Get updated value for a specific DataGridTemplateColumn in WPF

Goal
I am aiming to alter the selected value for a record and get the new value for a specific column in a DataGrid.
Right now, If I was to change a value in the Name column:
It detects the change:
Problem
When I change the position title, it does not show the new value.
Question
Why does it not detect the new value? And how do I do it?
What I have tried
I have tried to add OnPropertyChanged to all properties (except the override) for both models. This didn't do anything.
Code
Models
public class Person
{
public string Name { get; set; }
public Position Position { get; set; }
}
public class Position
{
public int PositionId { get; set; }
public string PositionTitle { get; set; }
public override bool Equals(object obj) =>
obj is Position p && PositionId == p.PositionId;
public override int GetHashCode() => PositionId.GetHashCode();
}
ViewModel
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
set
{
people = value;
OnPropertyChanged();
}
}
private ObservableCollection<Position> _positions;
public ObservableCollection<Position> Positions
{
get { return _positions; }
set
{
_positions = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
People = new ObservableCollection<Person>();
People.Add(new Person { Name = "Name 1", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 2", Position = new Position { PositionId = 1, PositionTitle = "Position Title 1" } });
People.Add(new Person { Name = "Name 3", Position = new Position { PositionId = 2, PositionTitle = "Position Title 2" } });
Positions = new ObservableCollection<Position>();
Positions.Add(new Position { PositionId = 1, PositionTitle = "Position Title 1" });
Positions.Add(new Position { PositionId = 2, PositionTitle = "Position Title 2" });
Command = new RelayCommand(param => EditData());
}
public ICommand Command { get; }
private void EditData()
{
var newData = People;
}
#region Prop Changed
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
View
<DataTemplate DataType="{x:Type local:MainViewModel}">
<StackPanel>
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Position Title">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Positions,
RelativeSource={RelativeSource AncestorType=DataGrid}}"
DisplayMemberPath="PositionTitle"
SelectedValue="{Binding Path=Position}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Save new data" Command="{Binding Command}" />
</StackPanel>
</DataTemplate>
Set the UpdateSourceTrigger of the SelectedValue binding to PropertyChanged:
SelectedValue="{Binding Path=Position, UpdateSourceTrigger=PropertyChanged}"

Background / Foreground Capability in Custom ListView Items

I have the following functioning code that binds GridViewColumns to data from a custom class:
<ListView Name="lv">
<ListView.View>
<GridView>
<GridViewColumn Header="First" DisplayMemberBinding="{Binding lvi.firstName}"/>
<GridViewColumn Header="Last" DisplayMemberBinding="{Binding lvi.lastName}"/>
</GridView>
</ListView.View>
</ListView>
public class LVItemBox {
public LVItem lvi { get; set; }
}
public class LVItem : INotifyPropertyChanged {
private string _firstName;
private string _lastName;
public string firstName {
get { return _firstName; }
set { SetField(ref _firstName, value); }
}
public string lastName {
get { return _lastName; }
set { SetField(ref _lastName, value); }
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName) {PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
LVItem lvi1 = new LVItem {firstName = "John", lastName = "Doe"};
LVItem lvi2 = new LVItem {firstName = "Jane", lastName = "Smith"};
lv.Items.Add(new LVItemBox {lvi = lvi1});
lv.Items.Add(new LVItemBox {lvi = lvi2});
}
}
My dilemma is that I want background / foreground Brush capability within LVItemBox, however if I make LVItemBox extend Control, changing Background/Foreground has no effect:
public class LVItemBox : Control {
public LVItem lvi { get; set; } // data displays
}
...
...
private void changeBackground(object sender, EventArgs e) {
LVItemBox lvib = (LVItemBox)lv.Items[0];
lvib.Background = Brushes.Black; // doesn't work
}
Furthermore, if I extend ListViewItem instead of Control I can get the background change to work, but the data bindings no longer work.
public class LVItemBox : ListViewItem {
public LVItem lvi { get; set; } // data doesn't display
}
...
...
private void changeBackground(object sender, EventArgs e) {
LVItemBox lvib = (LVItemBox)lv.Items[0];
lvib.Background = Brushes.Black; // works
}
Any idea how I can get foreground / background capability within LVItemBox?
Inheriting from Control works if you add the following ItemContainerStyle to your XAML:
<ListView Name="lv">
<ListView.View>
<GridView>
<GridViewColumn Header="First" DisplayMemberBinding="{Binding lvi.firstName}"/>
<GridViewColumn Header="Last" DisplayMemberBinding="{Binding lvi.lastName}"/>
</GridView>
</ListView.View>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Background" Value="{Binding Background}" />
</Style>
</ListView.ItemContainerStyle>
</ListView>

How to get ObservableCollection item by ListView SelectedIndex

I created a ListView and set a ObservableCollection to ItemsSource.
Then, I can use a int to binding SelectedIndex, it's work well.
But I don't know how to get item's detail when I selected.
I want to get ObservableCollection, then binding it to show on a TextBlock.
My TextBlock and ListView are difference UserControl. ViewModel in MainWindow.
So I want to know how to get ObservableCollection item by ListView SelectedIndex?
Or others method to resolve it?
Thx.
ViewModel in MainWinodw:
public class TestVM : INotifyPropertyChanged
{
private int _index;
public int Index
{
get
{
return _index;
}
set
{
_index = value;
RaisePropertyChanged("Index");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private ObservableCollection<User> _items;
public ObservableCollection<User> Items
{
get
{
return _items;
}
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
ObservableCollection<User> _collection;
public ObservableCollection<User> Collection
{
get
{
return _collection;
}
private set
{
_collection = value;
RaisePropertyChanged("Collection");
}
}
ListCollectionView _groupView;
public ListCollectionView GroupView
{
get
{
return _groupView;
}
private set
{
_groupView = value;
RaisePropertyChanged("GroupView");
}
}
public TestVM()
{
Collection = new ObservableCollection<User>();
Collection.Add(new User() { Name = "John Doe1", Age = 10, group = "Group 1" });
Collection.Add(new User() { Name = "Jane Doe2", Age = 20, group = "Group 1" });
Collection.Add(new User() { Name = "Sammy Doe", Age = 30, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe1", Age = 40, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe2", Age = 50, group = "Group 2" });
Collection.Add(new User() { Name = "Sammy Doe3", Age = 60, group = "Group 3" });
Collection.Add(new User() { Name = "Sammy Doe4", Age = 70, group = "Group 3" });
GroupView = new ListCollectionView(Collection);
GroupView.GroupDescriptions.Add(new PropertyGroupDescription("group"));
}
}
public class User
{
public string Name { set; get; }
public int Age { set; get; }
public string group { get; set; }
}
ListView in UserControl1:
<ListView Margin="10" Name="lv" ItemsSource="{Binding GroupView}" SelectedIndex="{Binding Index}">
<ListView.View>
<GridView>
<local:GridViewColumnExt Header="Name" Width="120" DisplayMemberBinding="{Binding Name}"/>
<local:GridViewColumnExt x:Name="colAge" Header="Age" Width="50">
<local:GridViewColumnExt.CellTemplate>
<DataTemplate>
<Button Content="{Binding Age}"></Button>
</DataTemplate>
</local:GridViewColumnExt.CellTemplate>
</local:GridViewColumnExt>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupHeaderStyle}">
</GroupStyle>
</ListView.GroupStyle>
</ListView>
TextBlock in UserControl2 (Just want to show item detail) :
<WrapPanel>
<TextBlock Text="SelectdIndex: "/>
<TextBlock Text="{Binding Index}" />
</WrapPanel>
MainWindow.xmal
<Grid>
<Grid.DataContext>
<local:TestVM/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<local:StepList Grid.Column="0"></local:StepList>
<local:ItemDetail Grid.Column="1"></local:ItemDetail>
</Grid>
If you want the details of the selected item in ListView to show up in the Textbox you have to set the binding in the Textbox.
e.g.
Text="{Binding SelectedItem.EnterThePropertyToShowhere, ElementName=EnterTheNameOfyourListViewhere, UpdateSourceTrigger="PropertyChanged"}"
Edit 3: Ok forgett what I said. Try:
Add to your Listview
SelectedItem="{Bindig SelectedUser , UpdateSourceTrigger="PropertyChanged"}"
Add to your ViewModel a property SelectedUser with propertychange Notification
public User SelectedUser{
get { return _selectedUser; }
set
{
if (value == _selectedUser) return;
_selectedUser= value;
RaisePropertyChanged("SelectedUser");
}
}
Add to your Textbox:
Text="{Binding SelectedUser.PropertyWhichShouldShow, UpdateSourceTrigger="PropertyChanged"}"

WPF New DataGrid Row Parameter NULL on Button Command

new to WPF here. This first app I'm building is using a RelayCommand and DataGrid with ButtonCommand to edit or enter a new user. For a new user with values entered into the open row, the OneditButtonCommand is being called when pressed, but the Item being passed as a parameter is always null. I tried switching the order of Command and CommandParameter in the XAML to see if the Command was being set before a defined parameter, but the button command object was still NULL. Does any solution jump out to anyone? Many thanks in advance!
The ViewModel:
public partial class UsersViewModel : INotifyPropertyChanged
{
public RelayCommand<UserViewModel> editButton_Click_Command { get; set; }
public UsersViewModel()
{
editButton_Click_Command = new RelayCommand<UserViewModel>(OneditButton_Click_Command);
this.Users = new ObservableCollection<UserViewModel>();
this.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe#yahoo.com", EndDate = new DateTime(2016,2,1), Position = "Developer", UserID = 0 });
}
private ObservableCollection<UserViewModel> _Users;
public ObservableCollection<UserViewModel> Users
{
get { return _Users; }
set { _Users = value; NotifyPropertyChanged("Users"); }
}
private void OneditButton_Click_Command(UserViewModel obj)
{
//Parameter object is always NULL here!!!
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
The XAML:
<Window x:Name="Base_V"
x:Class="DbEntities.UsersWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DbEntities"
xmlns:ViewModels="clr-namespace:DbEntities"
xmlns:staticData="clr-namespace:DbEntities"
mc:Ignorable="d"
Title="UsersWindow" Height="Auto" Width="900">
<Window.Resources>
<staticData:PositionsList x:Key="PositionsList" />
</Window.Resources>
<Window.DataContext>
<ViewModels:UsersViewModel/>
</Window.DataContext>
<Grid>
<FrameworkElement x:Name="dummyElement" Visibility="Collapsed" />
<DataGrid Name="DataGrid1" ItemsSource="{Binding Users}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch"
ColumnWidth="*" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.editButton_Click_Command, ElementName=Base_V}" CommandParameter="{Binding}" >Edit</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="User ID" Binding="{Binding UserID}" IsReadOnly="True" />
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
<DataGridTextColumn Header="E-Mail" Binding="{Binding EMail}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<Label Content="Position" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{StaticResource PositionsList}" SelectedItem="{Binding Position}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="End Date" Binding="{Binding EndDate, StringFormat={}{0:MM/dd/yyyy}}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The RelayCommand was taken from here: http://www.kellydun.com/wpf-relaycommand-with-parameter/
EDIT:
It was suggested that the UserViewModel be posted to see if the issue can be resolved here or in another question. There are a couple of test users pulled from a database as well.
UserViewModel:
public class UserViewModel : INotifyPropertyChanged
{
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set { _FirstName = value; NotifyPropertyChanged("FirstName"); }
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value; NotifyPropertyChanged("LastName"); }
}
private string _EMail;
public string EMail
{
get { return _EMail; }
set { _EMail = value; NotifyPropertyChanged("EMail"); }
}
private int _UserID;
public int UserID
{
get { return _UserID; }
set { _UserID = value; NotifyPropertyChanged("UserID"); }
}
private string _Position;
public string Position
{
get { return _Position; }
set { _Position = value; NotifyPropertyChanged("Position"); }
}
private DateTime? _EndDate;
public DateTime? EndDate
{
get { return _EndDate; }
set { _EndDate = value; NotifyPropertyChanged("EndDate"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
On the home page:
UsersViewModel Usersvm = new UsersViewModel();
Usersvm.Users = new ObservableCollection<UserViewModel>();
DbEntities db = new DbEntities();
var pulledUsers = db.uspGetNonAdminUsers().ToList();
foreach (var result in pulledUsers)
{
var pulledUser = new UserViewModel
{
FirstName = result.FirstName,
LastName = result.LastName,
EMail = result.Email,
UserID = result.UserID,
Position = result.Position,
EndDate = result.EndDate
};
Usersvm.Users.Add(pulledUser);
}
new UsersWindow(Usersvm).Show();
The UsersWindow:
public partial class UsersWindow : Window
{
public UsersWindow(UsersViewModel uvm)
{
InitializeComponent();
DataContext = uvm;
}
}
You need to do a few things:
Update your property to be type object:
public RelayCommand<object> editButton_Click_Command { get; set; }
update your instantiation to use object
editButton_Click_Command = new RelayCommand<object>(OneditButton_Click_Command);
then, update your event handler to have a param object and cast accordingly.
private void OneditButton_Click_Command(object obj)
{
var associatedViewModel = obj as UserViewModel;
if (associatedViewModel != null)
{
}
}

Categories