Remove row from DataGridView in WPF - c#

run into a problem, i have a datagrid, columns as follow
One, Two, Multiply
i can delete row buy clicking del KEY on the keyboard but when im trying to program my own remove function it does not work
and the message im getting is
An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
Additional information: Operation is not valid while ItemsSource is in use. Access and modify elements with ItemsControl.ItemsSource instead.
Here is my code:
private void buttonRemove_Click(object sender, RoutedEventArgs e)
{
NumberData.Items.Remove(NumberData.SelectedItems);
}
public class Numbers : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double? _one;
private double? _two;
private double? _multiply;
public double? One { get { return _one; } set { _one = value; UpdateValue(); } }
public double? Two { get { return _two; } set { _two = value; UpdateValue(); } }
public double? Multiply
{
get { return _multiply; }
set
{
_multiply = value; UpdateValue();
}
}
public Numbers(double? pOne, double? pTwo)
{
_one = pOne;
_two = pTwo;
_multiply = GetMultiply();
}
private void UpdateValue()
{
_multiply = GetMultiply();
OnPropertyChanged("One");
OnPropertyChanged("Two");
OnPropertyChanged("Multiply");
}
private double? GetMultiply()
{
return _one * _two;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public class Collection : ObservableCollection<Numbers>
{
public ObservableCollection<Numbers> Numbers { get; set; }
public Collection()
{
Numbers = new ObservableCollection<Numbers>();
//Numbers.Add(new Numbers(1, 2));
//Numbers.Add(new Numbers(2, 2));
}
public void AddNumbers(double pOne, double pTwo)
{
Numbers.Add(new Numbers(pOne,pTwo));
}
public void AddNumbersRow()
{
Numbers.Add(new Numbers(null, null));
}
}
<Grid>
<DataGrid x:Name="NumberData" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" ItemsSource="{Binding}" AutoGenerateColumns="False" LostFocus="StockData_LostFocus" >
<DataGrid.Columns >
<DataGridTextColumn Header="Number One" Width="100" Binding="{Binding One, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:C\}}" />
<DataGridTextColumn Header="Number Two" Width="100" Binding="{Binding Two, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:C\}}" />
<DataGridTextColumn Header="Total" Width="100" Binding="{Binding Multiply, BindsDirectlyToSource=True, Mode=TwoWay, StringFormat=\{0:C\}, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
<Button x:Name="buttonSave" Content="Save" HorizontalAlignment="Left" Margin="411,11,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<Button x:Name="buttonAddRow" Content="Add Row" HorizontalAlignment="Left" Margin="411,49,0,0" VerticalAlignment="Top" Width="75" Click="buttonAddRow_Click"/>
<Button x:Name="buttonRemove" Content="Remove" HorizontalAlignment="Left" Margin="411,88,0,0" VerticalAlignment="Top" Width="75" Click="buttonRemove_Click"/>
</Grid>

It looks like you have everything set up right, with a single oddity:
<DataGrid ItemsSource="{Binding}">
Not sure why the ItemsSource is the DataContext. The DataContext should be a View Model class with some ObservableCollection property that you then bind to.
But beyond that, since you said your code was working; you shouldn't be deleting items manually like that.
Your button should be bound to a command, that goes to the view model and modifies the bound collection. A sample would look like:
myObservableCollection.RemoveAt(0);
Since its an ObservableCollection, the modification will propagate to the UI.

Thanks a lot, i got the idea from your answers , here is what i have done to remove the item
if (NumberData.SelectedIndex == -1)
{
System.Windows.MessageBox.Show("nothing selected");
}
else
{
int indexSelected = Convert.ToInt32(NumberData.SelectedIndex);
mycollection.Numbers.RemoveAt(indexSelected);
}

Related

How to send gridview selected row data to another window with textboxes in wpf using mvvm

I have tried a bunch of solutions about this problem on google but none seem to be helpful.
I have a button on every row which when clicked open a new window with textboxes. This window should display the selected row cells data.
I load the datagrid from mysql database.
VIEW
textboxes (XML) for second window
<Label Content="{Binding sFirstName, Mode=OneWay }" /> <Label Content="{Binding sLastName, Mode=OneWay }" />
Datagrid
<DataGrid ItemsSource="{Binding Path=MM}" SelectionMode="Extended" SelectedItem="{Binding SelectedItem}" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=sFirstName}" />
<DataGridTextColumn Binding="{Binding Path=sLastName}" />
</DataGrid.Columns>
MODEL
public class MM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName]string PropertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName)); }
private string _sFirstName, _sLastName;
public string sFirstName { get { return _sFirstName; } set { if (_sFirstName != value) { _sFirstName = value; OnPropertyChanged("sFirstName"); } } }
public string sLastName { get { return _sLastName; } set { if (_sLastName != value) { _sLastName = value; OnPropertyChanged("sLastName"); } } }
public DataRowView SelectedRow
{
get { return SelectedRow; }
set { SelectedRow = value; OnPropertyChanged("SelectedItem"); }
}
}
VIEW MODEL
Public class MV : INotifyPropertyChanged
{
private ICommand cmdLoad;
public ICommand CmdLoad { get { if (cmdLoad == null) cmdLoad = new RelayCommand(p => OnLoad()); return cmdLoad; } }
private void OnLoad() { Load(); }
public ObservableCollection<FinTuitionM> finTuitionM { get; set; }
public ClrIdVMD()
{
Load();
}
public void Load()
{
}
}
Code behind (cs)
public partial class Home : Window
{
MV mv;
public Home()
{ InitializeComponent();
mv = new MV(); DataContext = mv;
}
}
You seem to be very confused, so I have prepared a small example of what I think you are trying to achieve.
I am guessing that you want to have a main view that is essentially read only, and you intend to use a popup to make changes. On this basis the View Model for the main window does not need to implement INotifyPropertyChanged. So a simple View Model would look like this:
public class MV
{
public ObservableCollection<MM> MMs { get; set; }
private ICommand cmdShowDetails;
public ICommand CmdShowDetails
{
get
{
if (cmdShowDetails == null) cmdShowDetails = new RelayCommand(p => ShowDetails());
return cmdShowDetails;
}
}
public void ShowDetails()
{
var detVM = new DetailsVM(SelectedItem);
var dets = new DetailsWindow(detVM);
dets.ShowDialog();
}
public MV()
{
MMs = new ObservableCollection<MM>
{
new MM{sFirstName = "Mickey", sLastName = "Mouse"},
new MM{sFirstName = "Donald", sLastName = "Duck"},
new MM{sFirstName = "Roger", sLastName = "Rabbit"},
};
}
public MM SelectedItem { get; set; }
}
Notice that for demonstration purposes, I have loaded the ObservableCollection with some dummy data. In your case, this is replaced with data from the database.
The MM class that this refers to then looks something like this:
public class MM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
if (PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChanged(this, e);
}
}
private string firstName;
public string sFirstName
{
get { return firstName; }
set
{
if (firstName == value)
{
return;
}
firstName = value;
RaisePropertyChangedEvent("sFirstName");
}
}
private string lastName;
public string sLastName
{
get { return lastName; }
set
{
if (lastName == value)
{
return;
}
lastName = value;
RaisePropertyChangedEvent("sLastName");
}
}
}
Notice that SelectedItem is in the View Model (MV) and is an object of class MM, so that when the second window is opened, the ShowDetails command can pass the selected details.
This therefore calls for a new very simple view model for the second (details) window:
public class DetailsVM
{
public MM Detail { get; set; }
public DetailsVM(MM detail)
{
Detail = detail;
}
}
The main window grid xaml now looks like this:
<Grid>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="Show Details" Command="{Binding CmdShowDetails}"></Button>
</StackPanel>
<DataGrid ItemsSource="{Binding MMs}" SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding sFirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding sLastName}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Grid>
Notice here that I only have one button at the bottom of the window to transfer the details. This is because the details come from the selected item, which is the highlighted row.
The code behind is simply:
public partial class MainWindow : Window
{
private MV _mV;
public MainWindow()
{
InitializeComponent();
_mV = new MV();
DataContext = _mV;
}
}
Finally the xaml for the second (details) window
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40*"/>
<RowDefinition Height="40*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70*"/>
<ColumnDefinition Width="200*"/>
</Grid.ColumnDefinitions>
<Label Content="First Name" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBox Text="{Binding Detail.sFirstName}" Grid.Column="1" Grid.Row="0" Width="150" Height="25" HorizontalAlignment="Left" />
<Label Content="Last Name" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center"/>
<TextBox Text="{Binding Detail.sLastName}" Grid.Column="1" Grid.Row="1" Width="150" Height="25" HorizontalAlignment="Left" />
</Grid>
Notice here that the binding is to Detail.sFirstName and Detail.sLastName. The DataContext is a DetailsVM object, which has a property Detail of type MM, hence sFirstName and sLastName are sub-properties of Detail.
This window also has a very simple code behind:
public partial class DetailsWindow : Window
{
private DetailsVM _details;
public DetailsWindow(DetailsVM details)
{
_details = details;
DataContext = _details;
InitializeComponent();
}
}
If you now run this, you will find that changes made in the second window are automatically reflected back into the main window. In practice you will probably want Save and Cancel buttons in the second window.
I hope the above is sufficient to point you in the right direction!

How can I highlight a DataGrid row programmatically from the ViewModel alone?

The SelectedItem property binding is not causing its DataGrid row to be highlighted on initial load.
I have a DataGrid with a binding on SelectedItem that is not getting highlighted until I click it again. I think I have an order of operations problem--coming from the fact that all the ViewModel code runs before the Views get rendered. It works fine once I click a row (even the same one that's already in the SelectedAccount prop), but I need to able to highlight a row from the VM.
I can easily verify the SelectedAccount property is not null because there are other ViewModels that display it's values via PubSubEvents.
I've tried several solutions methods, and the only way I've been able to get it to work so far just feels dirty:
using ApplicationName.UI.ViewModels;
public partial class AccountsView : UserControl
{
public AccountsView()
{
InitializeComponent();
Loaded += AccountsView_Loaded;
}
private void AccountsView_Loaded(object sender, RoutedEventArgs e)
{
AccountsViewModel viewModel = (AccountsViewModel)DataContext;
AccountsDataGrid.SelectedItem = viewModel.SelectedAccount;
AccountsDataGrid.Focus();
UpdateLayout();
}
}
I don't like this because it causes the OnPropertyChanged event to fire twice, once before the views load, and and again after the above hack. This triggers a SQL call, so I want to avoid that. I also thought the point of MVVM was to decouple the view from the viewmodel, but maybe I'm not understanding that as well as I thought.
Here's the XAML for the DataGrid:
<ScrollViewer Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Margin="5,0">
<DataGrid Name="AccountsDataGrid"
ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount}"
AutoGenerateColumns="False"
CanUserResizeColumns="True"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Header="ClinicId"
TextBlock.TextAlignment="Center"
Width="75"
Binding="{Binding ClinicId}" />
<DataGridTextColumn Header="Account#"
Width="75"
Binding="{Binding AccountNumber}"/>
<DataGridTextColumn Header="LastName"
Width="1*"
Binding="{Binding LastName}" />
<DataGridTextColumn Header="FirstName"
Width="1*"
Binding="{Binding FirstName}" />
<DataGridTextColumn Header="Balance"
Width="Auto"
Binding="{Binding Balance}" />
<DataGridTextColumn Header="Follow Up"
Width="100"
Binding="{Binding FollowUpDate}"/>
</DataGrid.Columns>
</DataGrid>
</ScrollViewer>
And the intial Load method in the ViewModel, which is where I want to set the highlighted row.
public void Load()
{
RefreshGrid();
SelectedAccount = Accounts.First();
_accountId = SelectedAccount.Id;
}
EDIT
The issue was subtle, but makes perfect sense now.
private Account _selectedAccount;
public Account SelectedAccount
{
get => _selectedAccount;
set => SetSelectedAccount(value);
}
private void SetSelectedAccount(Account value)
{
_selectedAccount = value;
OnPropertyChanged("_selectedAccount"); // <= whoops
if (_selectedAccount != null)
OnAccountSelected(
_selectedAccount.PrimaryKeyFields);
}
Raising this event for a private property doesn't make sense, as the view cannot see it, and is bound to SelectedAccount. Changing it to OnPropertyChanged("SelectedAccount") did the trick.
Implementing INotifyPropertyChanged should be enough, this code works on my end, I'm using a Command to call the Load() method but it's probably not needed in your code.
ViewModel and C# code :
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
Accounts = new List<Account>();
Accounts.AddRange(
Enumerable.Range(0, 10)
.Select(r => new Account
{
AccountNumber = r,
FirstName = $"First{r}",
LastName = $"Last{r}"
}));
LoadedCommand = new WpfCommand((param) => Load());
}
private void Load()
{
SelectedAccount = Accounts.FirstOrDefault(a => a.AccountNumber == 2);
}
public WpfCommand LoadedCommand { get; set; }
public List<Account> Accounts { get; set; }
private Account _selectedAccount = null;
public Account SelectedAccount
{
get { return _selectedAccount; }
set
{
_selectedAccount = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedAccount)));
}
}
}
public class Account
{
public int AccountNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class WpfCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public WpfCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public void Execute(object parameter)
{
_execute?.Invoke(parameter);
}
public bool CanExecute(object parameter)
{
return _canExecute?.Invoke(parameter) ?? true;
}
}
XAML :
<!--System.Windows.Interactivity.WPF nuget package-->
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
<DataGrid
ItemsSource="{Binding Accounts}"
SelectedItem="{Binding SelectedAccount}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AccountNumber}"/>
<DataGridTextColumn Binding="{Binding LastName}" />
<DataGridTextColumn Binding="{Binding FirstName}" />
</DataGrid.Columns>
</DataGrid>
In your view model, use your framework's RaisePropertyChanged(); function (or whatever the equivalent is). In the code-behind of the DataGrid element, try this code:
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
for (int i = 0; i < DataGrid.Items.Count; i++)
{
DataGridRow row = (DataGridRow)DataGrid.ItemContainerGenerator.ContainerFromIndex(i);
TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock;
if (cellContent != null && cellContent.Text.Equals(DataGrid.SelectedItem))
{
object item = DataGrid.Items[i];
DataGrid.SelectedItem = item;
DataGrid.ScrollIntoView(item);
row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
break;
}
}
}
In my example, I used a generic string list of names, so you may need to alter the line TextBlock cellContent = DataGrid.Columns[0].GetCellContent(row) as TextBlock; and cellContent.Text.Equals(DataGrid.SelectedItem)) to meet your line selection criteria.
The other alternative if you don't want to use the code-behind is an attached behavior that more-or-less does the same thing.

checkbox checked event trigger multiple time wpf mvvm

I got checkbox column in DataGrid that populating realtime download percentage in particular row, filtered by caseRefNo. i need to add checkbox changed event to perform some action. i used InvokeCommandAction to add the action to checkbox.
I realize that when i click the checkbox for the first time, It is ok and trigger only one time. but there are two times triggered when i click second time on the same checkbox.For third time clicking the same checkbox, it triggered four time. quite scary and difficult to figure it out.
here is my viewmodel code
public class DataGridDownloadViewModel:BindableBase
{
public ObservableCollection<tblTransaction> TransList { get; private set; }
public DispatcherTimer dispatchTimer = new DispatcherTimer();
public CollectionView TransView { get; private set; }
public DelegateCommand<object> CheckCommand { get; set; }
private String _UpdatePer;
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private string _caseId;
public string CaseID
{
get { return _caseId; }
set { SetProperty(ref _caseId, value); }
}
private string _isChecked;
public string isChecked
{
get { return _isChecked; }
set { SetProperty(ref _isChecked, value); }
}
private bool CanExecute(object args)
{
return true;
}
private void CheckBoxChecker(object args)
{
//Should Work Here
// Totally not coming to this function
CheckBox chk = (CheckBox)args;
string thichintae = chk.Name.ToString();
Console.WriteLine(thichintae);
}
public DataGridDownloadViewModel(List<tblTransaction> model)
{
CheckCommand = new DelegateCommand<object>(CheckBoxChecker, CanExecute);
dispatchTimer.Interval = TimeSpan.FromMilliseconds(3000);
dispatchTimer.Tick += dispatchTimer_Tick;
BackGroundThread bgT = Application.Current.Resources["BackGroundThread"] as BackGroundThread;
bgT.GetPercentChanged += (ss, ee) =>
{
UpdatePercentage = bgT.local_percentage.ToString();
};
bgT.GetCaseID += (ss, ee) =>
{
CaseID = bgT.local_caseRef;
};
TransList =new ObservableCollection<tblTransaction>(model);
TransView = GetTransCollectionView(TransList);
TransView.Filter = OnFilterTrans;
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var cancellationTokenSource = new CancellationTokenSource();
dispatchTimer.Start();
}
private void dispatchTimer_Tick(object sender, EventArgs e)
{
UpdateDataGrid();
}
public void UpdateDataGrid()
{
foreach (tblTransaction tran in TransList)
{
if (tran.caseRefNo == CaseID)
{
tran.incValue = int.Parse(UpdatePercentage);
}
else
{
tran.incValue = tran.incValue;
}
}
TransView.Refresh();
}
bool OnFilterTrans(object item)
{
var trans = (tblTransaction)item;
return true;
}
public CollectionView GetTransCollectionView(ObservableCollection<tblTransaction> tranList)
{
return (CollectionView)CollectionViewSource.GetDefaultView(tranList);
}
}
this is XAML for above view model.
<Window x:Class="EmployeeManager.View.DataGridDownload"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Title="DataGridDownload" Height="600" Width="790">
<Grid>
<DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransView}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
<DataGrid.Columns>
<DataGridTextColumn Header="caseRefNo" Binding="{Binding caseRefNo}" />
<DataGridTextColumn Header="subjMatr" Binding="{Binding subjMatr}" />
<DataGridTextColumn Header="Download %" Binding="{Binding incValue}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="abcdef"
Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction CommandParameter="{Binding ElementName=abcdef}" Command="{Binding DataContext.CheckCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGrid}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding incValue,UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Label Content="{Binding UpdatePercentage}" HorizontalAlignment="Left" Background="Blue" Foreground="White" Margin="10,10,0,0" VerticalAlignment="Top" Width="338" Height="30">
</Label>
<Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
Here is my model
public class tblTransaction
{
public string caseRefNo { get;set;}
public string subjMatr { get; set; }
public int incValue { get; set; }
public DateTime? longTime { get; set; }
public bool IsSelected { get; set; }
}
This is picture of my form
Is it because of DispatcherTimer ? All suggestion are welcome.
df
I think I left a comment in your previous question saying that wrapping your collection to CollectionView is quite smelly.
Anyway, the TransView.Refresh(); is causing the problem in your code.
TransView.Refresh will trigger the "Checked" event for each checked checkbox. The refresh basically asking the wpf engine re-populate all the data to your CollectionView and in turn, each checked checkbox will fire the checked event all over again.
Try setting dispatchTimer.Interval to a much shorter time e.g. 300. You should be able to see the "tick" in checkbox keep flicking becoz of the TransView.Refresh.
For real, I have no idea why you don't just bind your TransList to your DataGrid and called BeginInvoke on UpdateDataGrid method.
To demo how ObservableCollection works
I used the ObservableCollection to re-wrote some part of your code. At least make things better fall in line with MVVM model.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MainViewModel : ViewModelBase
{
public ObservableCollection<TblTransaction> TransList { get; private set; }
public DispatcherTimer DispatchTimer = new DispatcherTimer();
public MainViewModel()
{
var model = new ObservableCollection<TblTransaction>();
for (int i = 0; i < 5; i++)
{
model.Add(new TblTransaction { CaseRefNo = i.ToString(), IncValue = i, LongTime = DateTime.Now, SubjMatr = i.ToString() });
if (i == 3)
model[i].IsSelected = true;
}
DispatchTimer.Interval = TimeSpan.FromMilliseconds(200);
DispatchTimer.Tick += dispatchTimer_Tick;
TransList = model;
DispatchTimer.Start();
}
private void dispatchTimer_Tick(object sender, EventArgs e)
{
UpdateDataGrid();
}
public void UpdateDataGrid()
{
var ran = new Random();
foreach (var tran in TransList)
tran.IncValue = ran.Next(0, 100);
}
}
public class TblTransaction : ViewModelBase
{
private string caseRefNo;
private string subjMatr;
private int incValue;
private DateTime? longTime;
private bool isSelected;
public DelegateCommand<object> CheckCommand { get; set; }
public TblTransaction()
{
CheckCommand = new DelegateCommand<object>(CheckBoxChecker, (p) => true);
}
private void CheckBoxChecker(object args)
{
//Should Work Here
// Totally not coming to this function
//CheckBox chk = (CheckBox)args;
//string thichintae = chk.Name;
Console.WriteLine(args);
}
public string CaseRefNo
{
get { return caseRefNo; }
set
{
caseRefNo = value;
OnPropertyChanged();
}
}
public string SubjMatr
{
get { return subjMatr; }
set
{
subjMatr = value;
OnPropertyChanged();
}
}
public int IncValue
{
get { return incValue; }
set
{
incValue = value;
OnPropertyChanged();
}
}
public DateTime? LongTime
{
get { return longTime; }
set
{
longTime = value;
OnPropertyChanged();
}
}
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnPropertyChanged();
}
}
}
<Window x:Class="WpfTestProj.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"
xmlns:local="clr-namespace:WpfTestProj"
xmlns:interact="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=False}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid HorizontalAlignment="Left" ItemsSource="{Binding TransList}" AutoGenerateColumns="False" Margin="10,62,0,0" VerticalAlignment="Top" Height="497" Width="762">
<DataGrid.Columns>
<DataGridTextColumn Header="caseRefNo" Binding="{Binding CaseRefNo}" />
<DataGridTextColumn Header="subjMatr" Binding="{Binding SubjMatr}" />
<DataGridTextColumn Header="Download %" Binding="{Binding IncValue}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
Content="Please Select" IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<interact:Interaction.Triggers>
<interact:EventTrigger EventName="Checked">
<interact:InvokeCommandAction CommandParameter="{Binding Path=CaseRefNo}" Command="{Binding Path=CheckCommand}" />
</interact:EventTrigger>
</interact:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding IncValue, UpdateSourceTrigger=PropertyChanged}" Background="Red" Foreground="White" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Button" HorizontalAlignment="Left" Margin="672,20,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>

WPF DataGrid calculations

I am very new to WPF and C# so please help to solve this problem.
I have a DataGrid, columns as follows: "One", "Two", "Multiply".
The idea that I am entering number "One" or "Two" and get result in the column "Multiply".
When I write my code and debug it, I can see that the value of the property is being re-calculated. However, I would not display in my last column unless I push space bar, or click on my last column.
Here is the code:
public class Numbers : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _one;
private double _two;
private double _multiply;
public double One
{
get { return _one; }
set { _one = value; UpdateValue(); }
}
public double Two
{
get { return _two; }
set { _two = value; UpdateValue(); }
}
public double Multiply
{
get { return _multiply; }
set { _multiply = value; UpdateValue(); }
}
public Numbers(double pOne, double pTwo)
{
_one = pOne;
_two = pTwo;
_multiply = GetMultiply();
}
private void UpdateValue()
{
OnPropertyChanged("One");
OnPropertyChanged("Two");
OnPropertyChanged("Multiply");
_multiply = GetMultiply();
}
private double GetMultiply()
{
return _one * _two;
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class Collection : ObservableCollection<Numbers>
{
public ObservableCollection<Numbers> Numbers { get; set; }
public Collection()
{
Numbers = new ObservableCollection<Numbers>();
Numbers.Add(new Numbers(1, 2));
Numbers.Add(new Numbers(2, 2));
}
}
XAML:
<DataGrid x:Name="StockData" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" ItemsSource="{Binding}" AutoGenerateColumns="False" LostFocus="StockData_LostFocus" >
<DataGrid.Columns >
<DataGridTextColumn Header="Number One" Width="100" Binding="{Binding One, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:C\}}" />
<DataGridTextColumn Header="Number Two" Width="100" Binding="{Binding Two, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, StringFormat=\{0:C\}}" />
<DataGridTextColumn Header="Total" Width="100" Binding="{Binding Multiply, BindsDirectlyToSource=True, Mode=TwoWay, StringFormat=\{0:C\}, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
You are setting multiply after raising PropertyChanged event, so UI won't get notification that value has been changed.
Set Multiply and then raise PropertyChanged event:
private void UpdateValue()
{
_multiply = GetMultiply();
OnPropertyChanged("One");
OnPropertyChanged("Two");
OnPropertyChanged("Multiply");
}

How to use an observable collection and notifyproperty changed

I am new to wpf and i am currently trying to play with grid and adding subtracting,removing elements to a db table which eventually binds to a grid.
So select from grid, update observable collection, refresh.
I am failing to understand how can i use change notifications of observable collection.
Here is my code
Class which binds to grid
public class students
{
ObservableCollection<GetStudents_Result> stdb = new ObservableCollection<GetStudents_Result>();
//public event NotifyCollectionChangedEventHandler CollectionChanged;
public students()
{
AbcdEntities abcdEnt=new AbcdEntities();
List<GetStudents_Result> studentColl = abcdEnt.GetStudents().ToList();
foreach (var item in studentColl)
{
stdb.Add(item);
}
}
//public void onCollectionChange(object sender,NotifyCollectionChangedEventHandler e)
//{
//}
public ObservableCollection<GetStudents_Result> std {get {return stdb;}}
}
my xaml.
<Canvas>
<TextBox Height="23" Canvas.Top="5" Canvas.Left="10" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<TextBox Height="23" Canvas.Top="30" Canvas.Left="10" HorizontalAlignment="Left" Margin="10,10,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" />
<Button Canvas.Left="90" Canvas.Top="65" Content="Remove" Click="button2_Click" Height="23" Name="button2" Width="75" />
<Button Canvas.Left="10" Canvas.Top="65" Content="Save" Height="23" Name="button1" Width="75" Click="button1_Click" />
<ListView Name="listviewStudents" Canvas.Top="100" ItemsSource="{Binding std}" SelectionChanged="ListView_SelectionChanged">
<ListView.View>
<GridView>
<GridViewColumn Header="fname" DisplayMemberBinding="{Binding Path=fname}"></GridViewColumn>
<GridViewColumn Header="lname" DisplayMemberBinding="{Binding Path=lname}"></GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=address}"></GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=phno}"></GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Path=radio}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Canvas>
my code behind
public MainWindow()
{
InitializeComponent();
students std = new students();
this.DataContext = std;
}
private void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
GetStudents_Result selectedItem = listviewStudents.SelectedItem as GetStudents_Result;
textBox1.Text = selectedItem.fname;
textBox2.Text = selectedItem.lname;
}
private void button1_Click(object sender, RoutedEventArgs e)
{
GetStudents_Result selecteditem = listviewStudents.SelectedItem as GetStudents_Result;
selecteditem.fname = textBox1.Text;
selecteditem.lname = textBox2.Text;
listviewStudents.Items.Refresh();
}
private void button2_Click(object sender, RoutedEventArgs e)
{
listviewStudents.Items.Remove(listviewStudents.SelectedItem);
listviewStudents.Items.Refresh();
}
}
}
pardon for any stupid mistakes..
There a a coulpe of problems here, you should not have to touch the UI controls from the code behind, you should be using data binding.
Here is a working example of model binding based on your post.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
// set the DataContext to the code in this window
this.DataContext = this;
// create youe Student model
StudentModel = new students();
}
// Create a public property of your student Model
private students _studentModel;
public students StudentModel
{
get { return _studentModel; }
set { _studentModel = value; NotifyPropertyChanged("StudentModel"); }
}
// create a public property to use as the selected item from your models "std" collection
private GetStudents_Result _selectedResult;
public GetStudents_Result SelectedResult
{
get { return _selectedResult; }
set { _selectedResult = value; NotifyPropertyChanged("SelectedResult"); }
}
private void button2_Click(object sender, RoutedEventArgs e)
{
// if you want to remove an item you just have to remove it from
// the model, the INotifyPropertyChanged interface will notify the UI
// to update, no need to call Refresh, same works for Add etc
StudentModel.std.Remove(SelectedResult);
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Now in the xaml we can bind the ListView to your StudentModel collection and the SelectedResult
<ListView ItemsSource="{Binding StudentModel.std}" SelectedItem="{Binding SelectedResult}" >
And for the TextBoxes you can bind to the SelectedResult so it will update the details for you
Note: In this example it updates the SelectedResult when the text changes, you can change this as you wish.
<TextBox Text="{Binding SelectedResult.Fname, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding SelectedResult.Lname, UpdateSourceTrigger=PropertyChanged}" />
So now when you select an item from the ListView these TextBoxes will be populated, and when they are changed the SelectedResult item will be changed.
Now, for adding and removing Items in your ListView, you just have to add and remove from your StudentModelcollection (StudentModel.std).
private void button2_Click(object sender, RoutedEventArgs e)
{
StudentModel.std.Remove(SelectedResult);
}
Note: This event handler should be an ICommand binding, but i will let your search for that :)
Here is a Full example tha hopefully helps explaining the basics of WPF MVVM
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
StudentModel = new students();
}
private students _studentModel;
public students StudentModel
{
get { return _studentModel; }
set { _studentModel = value; NotifyPropertyChanged("StudentModel"); }
}
private GetStudents_Result _selectedResult;
public GetStudents_Result SelectedResult
{
get { return _selectedResult; }
set { _selectedResult = value; NotifyPropertyChanged("SelectedResult"); }
}
private void button2_Click(object sender, RoutedEventArgs e)
{
StudentModel.std.Remove(SelectedResult);
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class students
{
public students()
{
std = new ObservableCollection<GetStudents_Result>();
for (int i = 0; i < 100; i++)
{
std.Add(new GetStudents_Result { Fname = "FirstName" + i, Lname = "LasrName" + i });
}
}
public ObservableCollection<GetStudents_Result> std { get; set; }
}
public class GetStudents_Result : INotifyPropertyChanged
{
private string _fname;
private string _lname;
public string Fname
{
get { return _fname; }
set { _fname = value; NotifyPropertyChanged("Fname"); }
}
public string Lname
{
get { return _lname; }
set { _lname = value; NotifyPropertyChanged("Lname"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Xaml:
<Canvas>
<TextBox Text="{Binding SelectedResult.Fname, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding SelectedResult.Lname, UpdateSourceTrigger=PropertyChanged}" />
<Button Canvas.Left="90" Canvas.Top="65" Content="Remove" Click="button2_Click" Height="23" Name="button2" Width="75" />
<Button Canvas.Left="10" Canvas.Top="65" Content="Save" Height="23" Name="button1" Width="75" />
<ListView Name="listviewStudents" Canvas.Top="100" ItemsSource="{Binding StudentModel.std}" SelectedItem="{Binding SelectedResult}" >
<ListView.View>
<GridView>
<GridViewColumn Header="fname" DisplayMemberBinding="{Binding Path=Fname}"></GridViewColumn>
<GridViewColumn Header="lname" DisplayMemberBinding="{Binding Path=Lname}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Canvas>
I hope this info helps :)
Okay first, I apologize if I do a poor job answering this. It's my first attempt.
So it looks like you've got the right idea, and most of what you've got will work. But it looks like you may have forgotten to implement INotifyPropertyChanged. And you might just consider using something like List<GetStudent_Result> instead, there is a lot less overhead than an ObservableCollection because you'll be implementing the NotifyPropertyChanged stuff yourself.
public class students : INotifyPropertyChanged
{
#region PropertyChanged EventHandler
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(String Property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(Property));
}
#endregion
private List<GetStudents_Result> stdb;
public List<GetStudents_Result> std
{
get { return stdb; }
set { stdb = value; NotifyPropertyChanged("std"); }
}
...

Categories