I have a DataGrid which is using an ObservableCollection as the ItemsSource.
The items in the collection are items of a custom class.
When I edit one of those items, I can't get the view to update no matter what I do. I have tried removing and re-adding all of the items to the collection, and I have tried re-assigning the collection to the grid.
I know the edits are being saved correctly because I can see the changed values in the debugger.
What could possibly be causing this to happen?
Is there some other way I could force the grid to refresh?
As noted in the ObservableCollection documentation, only insertions and deletions are notified in the collection, which is exposed by the CollectionChanged event. In order to make the items in the ObservableCollection notify a change has been made, the underlying model must implement INotifyPropertyChanged:
Account:
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace TestUWP
{
public class Account : INotifyPropertyChanged
{
private string _accountName;
private decimal _amount;
public string AccountName
{
get => _accountName;
set
{
_accountName = value;
OnPropertyChanged();
}
}
public decimal Amount
{
get => _amount;
set
{
_amount = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModel + RelayCommand:
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace TestUWP
{
public class AccountViewModel
{
public AccountViewModel()
{
Accounts = new ObservableCollection<Account>
{
new Account {AccountName = "Account 1", Amount = 1000M},
new Account {AccountName = "Account 2", Amount = 2000M},
new Account {AccountName = "Account 3", Amount = 3000M},
};
AddAccountCommand = new RelayCommand(AddAccount);
EditAccountCommand = new RelayCommand(EditAccount);
}
public ICommand AddAccountCommand { get; }
public ICommand EditAccountCommand { get; }
public ObservableCollection<Account> Accounts { get; }
private void AddAccount()
{
Accounts.Add(new Account{AccountName = $"Account {Accounts.Count+1}", Amount = 1000M * (Accounts.Count+1)});
}
private void EditAccount()
{
Accounts[Accounts.Count - 1].Amount += 200M;
}
}
public class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool> _canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action execute, Func<bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object parameter) => _execute();
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
MainPage:
<Page
x:Class="TestUWP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestUWP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<local:AccountViewModel />
</Page.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ListView Grid.Column="0" Grid.RowSpan="2" ItemsSource="{Binding Accounts}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="x:String">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding AccountName}" Grid.Column="0" FontSize="30"/>
<TextBlock Text="{Binding Amount}" Grid.Column="1" Margin="30,0,0,0" FontSize="30"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Column="1" Grid.Row="0" Content="Add" FontSize="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding AddAccountCommand}"/>
<Button Grid.Column="1" Grid.Row="1" Content="Edit" FontSize="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding EditAccountCommand}"/>
</Grid>
</Page>
Changing Account from a POCO to one that implements the INotifyPropertyChanged allows the UI to refresh the Amount whenever the Edit button is clicked.
Alternatively, deleting and reinserting an item will also update the ItemSource, however this is not advisable due to the item will be appended to the end of the collection, and will then need custom sorting logic to say the least.
Related
I followed few online tutorials to switch between dynamic views from a ListView.
In my MainWindow, I have a ListView in the left pane with ItemsSource of list of sub ViewModels. (Each sub ViewModel implements an Interface)
Each ViewModel have its own View as a DataTemplate.
I'm calling the GenerateReport() method of the selected view from the MainWindow. But the values of selected views becomes null.
Download my source from Github.
Steps to reproduce the issue:
Run the application and type text in the Students Report's Id and Name. (The breakpoints in StudentReportViewModels's properties are properly hitting and values updated.)
Then click Generate Report button. The StudentReportViewModels's properties values becomes null.
How to fix the issue? Please help.
Source:
MainWindow.xaml
<Window.Resources>
<DataTemplate DataType="{x:Type vm:StudentsReportViewModel}">
<view:StudentsReport/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MarksReportViewModel}">
<view:MarksReport />
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListView ItemsSource="{Binding Reports}" SelectedItem="{Binding SelectedReport, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="5" ResizeBehavior="PreviousAndNext" HorizontalAlignment="Stretch"/>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<ContentControl Content="{Binding SelectedReport.ViewModel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</ScrollViewer>
<Button Content="Generate Report" Grid.Row="2" Margin="5" HorizontalAlignment="Right" Command="{Binding GenerateReportCommand}"/>
</Grid>
</Grid>
MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
private ICommand _generateReportCommand;
private Report selectedReport;
public Report SelectedReport
{
get
{
return this.selectedReport;
}
set
{
if (value != this.selectedReport)
{
this.selectedReport = value;
NotifyPropertyChanged();
}
}
}
public List<Report> Reports { get; set; }
public ICommand GenerateReportCommand
{
get
{
if (_generateReportCommand == null)
{
_generateReportCommand = new RelayCommand(
p => GenerateReport()
);
}
return _generateReportCommand;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public MainWindowViewModel()
{
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
SelectedReport = Reports[0];
}
public void GenerateReport()
{
SelectedReport.ViewModel.GenerateReport();
}
}
StudentsReport.xaml
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Height="25" Width="100" Margin="5" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
StudentsReportViewModel:
public class StudentsReportViewModel : INotifyPropertyChanged, IReportViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string id;
public string Id
{
get
{
return this.id;
}
set
{
if (value != this.id)
{
this.id = value;
NotifyPropertyChanged();
}
}
}
private string name;
public string Name
{
get
{
return this.name;
}
set
{
if (value != this.name)
{
this.name = value;
NotifyPropertyChanged();
}
}
}
public StudentsReportViewModel()
{
}
public void GenerateReport()
{
System.Windows.MessageBox.Show($"Id = {Id}, Name = {Name}");
}
}
Interface:
public interface IReportViewModel
{
void GenerateReport();
}
Model:
public class Report
{
public string Name { get; set; }
public IReportViewModel ViewModel { get; set; }
}
Your StudentsReport.xaml UserControl is binding to an instance of the StudentsReportViewModel created in the XAML:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
The Generate Report button however is calling into another instance of the StudentsReportViewModel that is create in the MainWindowVieModel constructor and is stored in the Report class.
Reports = new List<Report>
{
new Report{ Name = "Students Report", ViewModel = new StudentsReportViewModel()},
new Report{ Name = "Marks Report", ViewModel = new MarksReportViewModel()}
};
You need to remove one of these instances so that the UserControl's DataContext is bound to the same view model instance that you are generating the report message from.
I suggest deleting this code from StudentsReport.xaml:
<UserControl.DataContext>
<vm:StudentsReportViewModel/>
</UserControl.DataContext>
So I have got a view with a label and I have got a ViewModel for it.
ViewModelBase
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
This is how the ViewModel looks like:
private string _balance = "1111$";
public string Balance
{
get { return _balance; }
set { SetAndRaisePropertyChanged(ref _balance, value); }
}
And here is the view:
<UserControl x:Class="monei_project.MainUpperView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:project"
xmlns:vm="clr-namespace:project.ViewModels"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="2200" FontFamily="Open Sans">
<UserControl.Resources>
<vm:MainUpperViewModel x:Key="MainUpperViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource MainUpperViewModel}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Rectangle Grid.RowSpan="1" Grid.ColumnSpan="22" Fill="#013542"></Rectangle>
<Label Grid.Column="6" Grid.ColumnSpan="2" VerticalAlignment="Center" Foreground="White" FontSize="16">Balance:</Label>
<Label x:Name="lblBalance" Grid.Column="7" Grid.ColumnSpan="5" VerticalAlignment="Center" Foreground="White" FontFamily="Open Sans SemiBold" FontSize="24" Margin="55,28,45,33">
<Label.Content>
<Binding Path="Balance"/>
</Label.Content>
</Label>
</Grid>
In the designer, I can see the content of the label
But when I run the application, the label is empty
What is the problem?
I have already created some ViewModel, but there I worked with textboxes. We used INotifyPropertyChanged interface, and I am not sure how does it work, so my guess was, that it sets the content, but won't display it, because the label is not updating, so I tried to use the OnPropertyChanged function with a PropertyChangedEventHandler, what we used earlier to the other ViewModels, but it didn't work either, I do not know what can be wrong.
There are some frameworks providing class already implementing the needed interfaces, if you want to do it yourself, here is a possibility:
First you have your ViewModelBase and all your ViewModels should inherit it
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
}
then in your viewmodel you will declare your property like this:
private String _mBalance;
public String Balance
{
get { return _mBalance; }
set => SetAndRaisePropertyChanged(ref _mBalance, value);
}
[EDIT]: I want to keep the history of the answer, so check my edit below with full fonctionnal example:
Usually I split in more files, but i wanted to stay simple, so you need 2 files (I'm trying to apply MVVM pattern so i'm adding directories):
- Views\MainWindow.xaml
- ViewModels\MainWindowViewModel.cs
Views\MainWindow.xaml:
<Window x:Class="StackOverflow_DBG.MainWindow"
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:StackOverflow_DBG"
xmlns:viewmodels="clr-namespace:StackOverflow_DBG.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="400">
<Window.DataContext>
<viewmodels:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Row="1" Grid.Column="0" Content="{Binding LabelTxt}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ValueTxt}"/>
<Button Grid.Row="1" Grid.Column="2" Content="Change Label" Command="{Binding ChangeLabel}"/>
</Grid>
</Window>
ViewModels\MainWindowViewModel.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace StackOverflow_DBG.ViewModels
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetAndRaisePropertyChanged<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
}
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
class MainWindowViewModel : ViewModelBase
{
private String m_LabelTxt = "Foo";
public String LabelTxt
{
get { return m_LabelTxt; }
set => SetAndRaisePropertyChanged(ref m_LabelTxt, value);
}
private String m_ValueTxt;
public String ValueTxt
{
get { return m_ValueTxt; }
set => SetAndRaisePropertyChanged(ref m_ValueTxt, value);
}
private RelayCommand m_ChangeLabel;
public RelayCommand ChangeLabel
{
get { return m_ChangeLabel; }
set { m_ChangeLabel = value; }
}
public MainWindowViewModel()
{
ChangeLabel = new RelayCommand(() => {
if (LabelTxt == "Foo")
{
LabelTxt = "Bar ";
}
else
{
LabelTxt = "Foo ";
}
});
}
}
}
This way you also see how to bind button for example. Press the button to see that the update is well done.
If using same directories than me, remember to edit app.xaml to use StartupUri="Views/MainWindow.xaml">
instead of
StartupUri="MainWindow.xaml">
Have you properly set the DataContext on the window/control to your view model? You need to set your DataContext before you are able to use bindings. And as such, you should probably use the proper way for binding:
<Label Content="{Binding Balance}" ... />
Edit:
Okay, I'll give you a concrete example of what I mean. Also, you're going to run into a lot of issues using a view model as a StaticResource. Why do I say this? Because once you start adding dependencies to your view model (accessing business logic, etc), you will need some form of dependency injection (DI) or a similar way to do so.
So you have your ViewModelBase, so I'll use that and not duplicate myself. Here's a simple view model:
public class AccountViewModel : ViewModelBase
{
string _balance = "1111$";
public AccountViewModel(string accountNumber)
{
AccountNumber = accountNumber;
}
public string AccountNumber { get; }
public string Balance
{
get { return _balance; }
set { SetAndRaisePropertyChanged(ref _balance, value); }
}
}
Here's the view code (MainWindow.xaml):
<Window x:Class="testProj.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
<Grid>
<Label Content="{Binding Balance}" />
</Grid>
</Window>
And the code behind (MainWindow.xaml.cs):
public partial class MainWindow
{
public MainWindow(AccountViewModel dataContext)
{
DataContext = dataContext;
InitializeComponent();
}
}
And for fun, the App.xaml.cs (set up for BuildAction - Page):
public partial class App
{
[STAThread]
public static void Main(string[] args)
{
new MainWindow(new AccountViewModel("123456789")).ShowDialog();
}
}
This will show you what you are expecting, and display the Balance correctly. There are a few things you can try to see what your issue is:
Is there any information in the output window when debugging that tells you if a binding error is occurring?
Can you give a shorter version of your application showing all parts (i.e. a short project that duplicates the issue) and upload it somewhere?
Delete the margin solved the problem. I guess the margin pushed out the label.
I have 3 text boxes and when user enter value in them and press save button then they should add the data they contain to the data grid.
Every thing works fine and binding to the textboxes and button is done well but i do not understand how to update datagrid using the value user entered in textboxes.
My full code is here :
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding TextName}" Height="20" Width="80" HorizontalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding RollNumber}" Height="20" Width="80"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Class}" Height="20" Width="80"></TextBox>
<Label Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center">Name</Label>
<Label Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center">RollNumber</Label>
<Label Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center">Class</Label>
</Grid>
<Grid Grid.Row="1" >
<Button Width="80" Height="20" Command="{Binding SaveStudentRecord}"> Save</Button>
</Grid>
<Grid Grid.Row="2">
<DataGrid ItemsSource="{Binding DGrid}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding DgName}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Rollnumber" Binding="{Binding dgRollnumber}" Width="150"></DataGridTextColumn>
<DataGridTextColumn Header="Class" Binding="{Binding dgClass}" Width="150"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
</Window>
ViewModel is:
class ViewModel
{
private string textName;
private string rollNumber;
private string cclass;
private RelayCommand saveStudentRecord;
private Model editModel;
public string TextName
{
get { return textName; }
set
{
textName = value;
PropertyChangedEventArgs("TextName");
}
}
public string RollNumber
{
get { return rollNumber; }
set
{
rollNumber = value;
PropertyChangedEventArgs("RollNumber");
}
}
public string Class
{
get { return cclass; }
set
{
rollNumber = value;
PropertyChangedEventArgs("Class");
}
}
public bool canExecute { get; set; }
public Model EditModel
{
get
{
return editModel ;
}
set
{
editModel = value;
PropertyChangedEventArgs("EditModel");
}
}
public ViewModel()
{
canExecute = true;
}
public RelayCommand SaveStudentRecord
{
get { return saveStudentRecord = new RelayCommand(() => MyAction(), canExecute); }
}
private void MyAction()
{
string chck1 = TextName; //I see on debugging that TextName contains the text entered so how to add this text to Datagrid column
string chck2 = Class;
string chck3 = RollNumber;
// How to add this data to datagrid
MessageBox.Show("Hii");
}
public event PropertyChangedEventHandler PropertyChanged;
private void PropertyChangedEventArgs(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What exactly i mean is how to bind the datagrid inside the MyAction() such that all the three textbox strings will be added to the respective columns ?
As others have suggested in comments I'm not sure what Dgrid represents, but I think a simple example should help:
This is just a window with a DataGrid, TextBox and a button. When you type something in the datagrid and press the button it adds the value to the datagrid. It's done MVVM, I hope this demonstrates the process.
(.NET 4.6 syntax. If it doesn't work change the observable collection and move the creation of it to the constructor)
MainView.xaml
<Window x:Class="WpfApplication4.MainView"
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"
mc:Ignorable="d"
Title="MainView" Height="350" Width="525">
<Grid>
<DataGrid HorizontalAlignment="Left" Height="207" Margin="103,46,0,0" VerticalAlignment="Top" Width="311" ItemsSource="{Binding Stuff}" AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}"/>
</DataGrid.Columns>
</DataGrid>
<TextBox HorizontalAlignment="Left" Height="20" Margin="167,9,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="179" Text="{Binding TextValue, Mode=TwoWay}"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="351,8,0,0" VerticalAlignment="Top" Width="75" Command="{Binding GoCommand}"/>
</Grid>
</Window>
MainViewModel.cs
using System.Collections.ObjectModel;
using System.Windows.Input;
namespace WpfApplication4
{
public class MainViewModel
{
public ObservableCollection<string> Stuff { get; set; } = new ObservableCollection<string>();
public ICommand GoCommand { get; set; }
public string TextValue { get; set; }
public MainViewModel()
{
Stuff.Add("a");
Stuff.Add("b");
Stuff.Add("c");
Stuff.Add("d");
GoCommand = new RelayCommand((p) => Stuff.Add(TextValue));
}
}
}
MainView.xaml.cs
using System.Windows;
namespace WpfApplication4
{
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
RelayCommand.cs
using System;
using System.Diagnostics;
using System.Windows.Input;
namespace WpfApplication4
{
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null) throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return canExecute == null || canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
execute(parameter);
}
}
}
I got here a template combo box
<ComboBox x:Name="TryCombo" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="75"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,0" Visibility="Collapsed" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,0" Grid.Column="1"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
then the ItemsSource:
public void BindComboboxes()
{
itemMgr.Parameters = RetrieveFilter("");
itemMgr.EntityList = itemMgr.RetrieveMany(itemMgr.Parameters);
TryCombo.ItemsSource = itemMgr.EntityList; //collection;
}
the combo box will load this:
My problem is getting the selected item that I had clicked with the AddButton, I want to get the value of text block that was bound to Path=Id, but how?
Should I put x:Name for each of the TextBlocks?
Bind your Button to a ICommand-Property in your ViewModel and pass the selected Item as CommandParameter
I've made a small Demo-Application for you:
The MainWindow.xaml looks like:
<Window x:Class="ComboBoxDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
WindowStartupLocation="CenterScreen"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Content="Add Item to ComboBox" Margin="5" Command="{Binding AddItemCommand}"/>
<ComboBox Grid.Row="1" x:Name="TryCombo" ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}" Margin="5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,2" VerticalAlignment="Center" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,2" Grid.Column="1" VerticalAlignment="Center"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2" VerticalAlignment="Center" Margin="4,2"
Command="{Binding DataContext.ComboBoxItemAddCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Important is the part in the Window-Declaration of: DataContext="{Binding RelativeSource={RelativeSource Self}}" With this you tell the Window that it's DataContext is in the CodeBehind-File. You also can use another File as your DataContext. Than you have to write:
<Window.DataContext>
<loc:MyClassName/>
</Window.DataContext>
This only works if you add xmlns:loc="clr-namespace:YOURPROJECTNAMESPACE" to the Window-Declaration. In the case of my Demo YOURPROJECTNAMESPACE would be ComboBoxDemo.
I've also created a class for the Parameter which is very simple and looks like:
public class Parameter
{
public string Id { get; set; }
public string Name { get; set; }
}
The CodeBehind of the Window (remember: This is the DataContext of the MainWindow.xaml) looks like:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ComboBoxDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ICommand addItemCommand;
private ICommand comboBoxItemAddCommand;
private ObservableCollection<Parameter> parameters;
public MainWindow()
{
InitializeComponent();
Parameters = new ObservableCollection<Parameter>();
AddItemCommand = new RelayCommand(AddItem);
ComboBoxItemAddCommand = new RelayCommand(ComboBoxItemAdd);
}
private void ComboBoxItemAdd(object parameter)
{
Parameter para = parameter as Parameter;
if (para != null)
{
// Now you can use your Parameter
}
}
public ObservableCollection<Parameter> Parameters
{
get { return parameters; }
set
{
parameters = value;
OnPropertyChanged();
}
}
public ICommand AddItemCommand
{
get { return addItemCommand; }
set
{
addItemCommand = value;
OnPropertyChanged();
}
}
public ICommand ComboBoxItemAddCommand
{
get { return comboBoxItemAddCommand; }
set
{
comboBoxItemAddCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void AddItem(object parameter)
{
Parameters.Add(new Parameter
{
Id = Guid.NewGuid().ToString(),
Name = "Any Name"
});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I also created a very useful Helper-Class which you will likely always need if you are using command-binding. This is the class RelayCommand. This class has a constructer which wants an object of type Action<object>. This action later contains what will be executed when you click the button. The seconde optional parameter I will not explain now. The RelayCommand looks like:
using System;
using System.Windows.Input;
namespace ComboBoxDemo
{
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null )
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
return true;
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
So. Now I will explain you the process: Therefor I use the following abbreviations
V = MainWindow.xaml
VM = MainWindow.xaml.cs
The ComboBox in the V has your given Itemtemplate. In the definition of the ComboBox I've added ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}". This tells the ComboBox that it will get it's children from this collection which is located in the VM.
The collection in the VM is an ObservableCollection<Parameter>. The advantage of this type of Collection is, that it implements the ICollectionChanged-Interface and so your V will get updated if there are items added or removed to this collection.
The Button in the V just adds a dummy-Parameter to the ObservableCollection<Parameter>.
With Command="{Binding AddItemCommand}" I tell the Button that it's command-property is bound to the AddItemCommand in the DataContext. In the constructor of the DataContext (MainWindow.xaml.cs) I'm creating this Command and provide the AddItem-Method which will be called if the command is executed.
The Binding of the Button in the DataTemplate must provide a RelativeSource because inside the Template the DataContext is another one. With the RelativeSource I can tell the Button that its Command-Property is bound to a Command which is located in the DataContext of the Window.
I hope this helps you.
If you want to go deeper into the MVVM-Pattern take a look at this Link
One quick way to do it is to simply bind the Tag property of the button to the same property as the TextBlock.
<Button Tag="{Binding Path=Id}" />
Then in the event handler for the button click event you can cast the sender to Button and get the ID from the Tag property.
int id = Convert.ToInt32((sender as Button).Tag);
I was working on dynamic generation of labels, buttons and Textbox in my WPF application. Well I was successful in dynamically creating them but I am facing one major issue in it.
Xaml:
<ListBox x:Name="myViewChannelList" HorizontalAlignment="Stretch" Height="Auto" ItemsSource="{Binding}" Margin="0" VerticalAlignment="Stretch" Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="170" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" Margin="50,20,0,0"></Label>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=VoltageText}" Height="25" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" />
<Button Grid.Column="1" Content="Set" Height="25" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" Width="50" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,0,0,0" ></Button>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Model Class:
private string _ChanelName = "";
public String ChanelName
{
get
{
return _ChanelName;
}
set
{
if (value != _ChanelName)
{
_ChanelName = value;
OnPropertyChanged("ChanelName");
}
}
}
// Constructor
public VoltageModel(string ChanelName)
{
this.ChanelName = ChanelName;
}
public override string ToString()
{
return _ChanelName;
}
ViewModel Class:
class ChannelList : ObservableCollection<VoltageModel>, INotifyPropertyChanged
{
private string _VoltageText;
public string VoltageText
{
get { return _VoltageText; }
set
{
_VoltageText = value;
OnPropertyChanged("VoltageText");
}
}
// Method gets called when Set Button Is Clicked
public void SetCommandExecuted()
{
string val = VoltageText;
}
//Notify Property Changed members are present
}
Xaml.cs Class:
ChannelList myChanels = new ChannelList();
public VoltageView() // Constructor
{
InitializeComponent();
myChanels.Add(new VoltageModel("VDD__Main"));
myChanels.Add(new VoltageModel("VDD__IO__AUD"));
myChanels.Add(new VoltageModel("VDD__CODEC__AUD"));
myViewChannelList.DataContext = myChanels;
}
This gives me 3 Labels(Content as above), 3 textboxes and 3 buttons when I run the application.
Now when I enter the value inside the textbox it shows null on button click when I put a breakpoint in SetCommandExecuted(). Most importantly any of the 4 button I click generates the event. I want the first textbox and first button to be in sync(bind), 2nd textbx and 2nd button to be in sync and so on. Basically each control must be in sync with the other control in a row. It should not effect the other rows. Is it possible???
Here is the solution to your question. As general practice you want to avoid all logic, building your data, etc. in the code behind. All the business logic should be in the view model which will make it easier to unit test.
Here is the view
.xaml
<StackPanel>
<ListBox HorizontalAlignment="Stretch"
Height="Auto"
ItemsSource="{Binding VoltageCollection}"
VerticalAlignment="Stretch"
Width="Auto">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Width="100"
Content="{Binding ChannelName}" />
<TextBox Width="100"
Text="{Binding VoltageText}" />
<Button Margin="10,0,0,0"
Content="Set"
Command="{Binding VoltageCommand}"
CommandParameter="{Binding VoltageText}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
Here is the code behind
.xaml.cs
private ChannelListViewModel m_voltageViewModel;
public MainWindow()
{
InitializeComponent();
m_voltageViewModel = new ChannelListViewModel();
m_voltageViewModel.Initialize();
DataContext = m_voltageViewModel;
}
Here is the Model: VoltageModel
public class VoltageModel : INotifyPropertyChanged
{
public string ChannelName { get; set; }
private string m_voltageText;
public string VoltageText
{
get { return m_voltageText; }
set
{
m_voltageText = value;
OnPropertyChanged("VoltageText");
}
}
public ICommand VoltageCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Here is the ViewModel: ChannelListViewModel
public class ChannelListViewModel
{
private ICommand m_voltageCommand;
public ChannelListViewModel()
{
m_voltageCommand = new DelegateCommand(x => SetCommandExecute(x));
}
public void Initialize()
{
VoltageCollection = new ObservableCollection<VoltageModel> { new VoltageModel() { ChannelName = "VDD__Main", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__IO__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand },
new VoltageModel() { ChannelName = "VDD__CODEC__AUD", VoltageText = String.Empty, VoltageCommand = m_voltageCommand }};
}
public ObservableCollection<VoltageModel> VoltageCollection { get; set; }
public void SetCommandExecute(object voltageText)
{
Debug.WriteLine(voltageText);
}
}
Finally simple DelegateCommand class DelegateCommand
public class DelegateCommand : ICommand
{
Action<object> m_executeDelegate;
public DelegateCommand(Action<object> executeDelegate)
{
m_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
m_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
i didn't get much into what was wrong since i recognized 2 things that were generally very wrong ,and they might be the problem for unexpected behavior on your part .
the first : your DataTemplate places your control one on top of the other .
fix :
<DataTemplate >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Path=ChanelName}" />
<TextBox Grid.Column="1" Text="{Binding Path=VoltageText}" />
<Button Grid.Column="2" Command="{Binding ElementName=myViewChannelList, Path=DataContext.SetCommand}" />
</Grid>
</DataTemplate>
the second : your Properties are set after PropertyChanged event was risen so they would not be updated until the next time you input a value.
fix :
private T _property;
public T Property
{
get { return _property; }
set
{
_property = value;
OnPropertyChanged("Property");
}
}
make these fixes and edit your post if you still have issues post a comment under my answer.