wpf Datagrid: throw duplicated entry - c#

In my application I want to validate if the user enter an item which is already exist on the DataGrid when I enter a new Item on the cell. I validate my business object using IDataErrorInfo.
My object is as follows:
class clsProducts : INotifyPropertyChanged, IDataErrorInfo
{
private string _ProductName;
private decimal _PurchaseRate;
private int _AvailableQty;
private int _Qty;
private decimal _Amount;
#region Property Getters and Setters
public string ProductName
{
get { return _ProductName; }
set
{
if (_ProductName != value)
{
_ProductName = value;
OnPropertyChanged("ProductName");
}
}
}
public decimal PurchaseRate
{
get { return _PurchaseRate; }
set
{
_PurchaseRate = value;
OnPropertyChanged("PurchaseRate");
}
}
public int AvailableQty
{
get { return _AvailableQty; }
set
{
_AvailableQty = value;
OnPropertyChanged("AvailableQty");
}
}
public int Qty
{
get { return _Qty; }
set
{
_Qty = value;
this._Amount = this._Qty * this._PurchaseRate;
OnPropertyChanged("Qty");
OnPropertyChanged("Amount");
}
}
public decimal Amount
{
get { return _Amount; }
set
{
_Amount = value;
OnPropertyChanged("Amount");
}
}
#endregion
#region IDataErrorInfo Members
public string Error
{
get
{
StringBuilder error = new StringBuilder();
// iterate over all of the properties
// of this object - aggregating any validation errors
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(this);
foreach (PropertyDescriptor prop in props)
{
string propertyError = this[prop.Name];
if (!string.IsNullOrEmpty(propertyError))
{
error.Append((error.Length != 0 ? ", " : "") + propertyError);
}
}
return error.ToString();
}
}
public string this[string name]
{
get
{
string result = null;
if (name == "ProductName")
{
if (this._ProductName != null)
{
int count = Global.ItemExist(this._ProductName);
if (count == 0)
{
result = "Invalid Product "+this._ProductName;
}
}
}
else if (name == "Qty")
{
if (this._Qty > this._AvailableQty)
{
result = "Qty must be less than Available Qty . Avaialble Qty : " + this._AvailableQty;
}
}
return result;
}
}
#endregion
#region INotifyPropertyChanged Members
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
//// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
My xaml is :
<my:DataGrid Name="dgReceiveInventory" RowStyle="{StaticResource RowStyle}" ItemsSource="{Binding}" GotFocus="dgReceiveInventory_GotFocus" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserResizeRows="False" CanUserSortColumns="False" RowHeight="23" SelectionUnit="Cell" AutoGenerateColumns="False" Margin="12,84,10,52" BeginningEdit="dgReceiveInventory_BeginningEdit">
<my:DataGrid.Columns>
<!--0-Product Column-->
<my:DataGridTemplateColumn Header="Product Name" Width="200">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlockInError}" Text="{Binding ProductName,ValidatesOnDataErrors=True}" ></TextBlock>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
<my:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox x:Name="txtbxProduct" Style="{StaticResource TextBoxInError}" Text="{Binding Path=ProductName,UpdateSourceTrigger=LostFocus,ValidatesOnDataErrors=True}" TextChanged="txtbxProduct_TextChanged" PreviewKeyDown="txtbxProduct_PreviewKeyDown" >
</TextBox>
</DataTemplate>
</my:DataGridTemplateColumn.CellEditingTemplate>
</my:DataGridTemplateColumn>
<!--1-Purchase Rate Column-->
<my:DataGridTextColumn Header="Purchase Rate" Width="100" Binding="{Binding PurchaseRate}" IsReadOnly="True"></my:DataGridTextColumn>
<!--2-Avaialable Qty Column-->
<my:DataGridTextColumn Header="AvailableQty" Binding="{Binding AvailableQty}" IsReadOnly="True" Visibility="Hidden"></my:DataGridTextColumn>
<!--3-Qty Column-->
<my:DataGridTextColumn Header="Qty" Binding="{Binding Qty,ValidatesOnExceptions=True,ValidatesOnDataErrors=True}" EditingElementStyle="{StaticResource TextBoxInError}">
</my:DataGridTextColumn>
<!--4-Amount Column-->
<my:DataGridTextColumn Header="Amount" Width="100" Binding="{Binding Amount}" ></my:DataGridTextColumn>
</my:DataGrid.Columns>
</my:DataGrid>
Now I want to show the user ,if he made a duplicate entry in the datagrid cell how to do this ?

You can't do this functionality in your model or data type class using the IDataErrorInfo interface because you won't have access to the other objects there. Instead, you'll have to do it in your view model. However, you can report the error using that interface. I have extended its functionality by adding a property into my data type base class:
public virtual ObservableCollection<string> ExternalErrors
{
get { return externalErrors; }
}
As you can see, mine deals with multiple errors, but you can easily change this to:
public virtual string ExternalError
{
get { return externalError; }
}
Then I 'plug' this into my Errors property:
public override ObservableCollection<string> Errors
{
get
{
errors = new ObservableCollection<string>();
errors.AddUniqueIfNotEmpty(this["Name"]);
errors.AddUniqueIfNotEmpty(this["EmailAddresses"]);
errors.AddUniqueIfNotEmpty(this["StatementPrefixes"]);
errors.AddRange(ExternalErrors);
return errors;
}
}
Again, I have adapted this interface to return multiple errors, but you can change this to:
public override string Error
{
get
{
error = string.Empty;
if ((error = this["Name"])) != string.Empty) return error;
if ((error = this["EmailAddresses"])) != string.Empty) return error;
if ((error = this["Name"])) != string.Empty) return error;
if (ExternalError != string.Empty) return ExternalError;
return error;
}
}
Incidentally, it is far more efficient to call just the indexers that you are actually validating rather than your example of calling all properties using reflection. However, that is your choice.
So now that we have this ExternalError property, we can use it to display external error messages from your view model (create a class that contains a collection property to bind to the DataGrid.ItemsSource property).
If you are using ICommand objects, then you can put this code into the CanExecute method of your Save command:
public bool CanSave(object parameter)
{
clsProducts instance = (clsProducts)parameter;
instance.ExternalError = YourCollectionProperty.Contains(instance) ?
"The values must be unique" : string.Error;
// Perform your can save checks here
}
Please note that you will need to implement the Equals method in your data type object for this to work. There are many similar ways to achieve this and I trust that from this example you will be able to work one out that works for you.

Related

DataGridTemplateColumn update

I'm reworking an old control to WPF. I need to make a datagrid which displays a list of alarms. The first column of the datagrid is a datagridtemplate column which holds a flag icon and the date. When the user presses the button the alarms are acknowledged and the flag icon has to disappear. I'm using an MVVM pattern.
The problem is that I cannot seem to update the flag in the datagridtemplate column. When I change the acknowledge status from 0 to 1 (the alarm was ackonwledged), the flag does not disappear. When I reload the control and get the alarms again from the server, the flag does not show up again indicating that its status has actually been updated in the server. So somehow my code is working but my UI is not getting updated. I also tried to change the alarm's description in the same way and that column does get updated. (MainViewModelBase implements INotifyPropertyChanged)
The view
<DataGrid x:Class="Kwa.Presentation.Views.AlarmList.AlarmList"
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:Kwa.Presentation.Views.AlarmList"
xmlns:components="clr-namespace:Kwa.Presentation.Components"
xmlns:converters="clr-namespace:Kwa.Presentation.Converters"
xmlns:Trans="clr-namespace:Kwa.Presentation.Resources"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750"
ItemsSource="{Binding Alarms}"
SelectedItem="{Binding SelectedAlarm}"
IsSynchronizedWithCurrentItem="True"
CanUserResizeColumns="True" IsReadOnly="True" CanUserReorderColumns="False" CanUserSortColumns="False" SelectionMode="Single" CanUserAddRows="False"
Background="White" RowHeaderWidth="0" AutoGenerateColumns="False" GridLinesVisibility="None" RowHeight="{Binding Rowheight}" FrozenColumnCount = "1"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
x:Name="AlarmFramework"
SizeChanged="AlarmFramework_SizeChanged"
>
<DataGrid.Resources>
<converters:AlarmSeverityToColorConverter x:Key="AlarmSeverityToColorConverter"/>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<converters:InvertedBoolToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderTime}" Width="auto" HeaderStyle="{StaticResource WithButt}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<components:FlagControl VerticalAlignment="Center" Height="15" Width="15" FlagColor="{Binding Severity, Converter={StaticResource AlarmSeverityToColorConverter}}"
Visibility="{Binding AckStatus, Converter={StaticResource InvertedBoolToVisibilityConverter}, Mode=TwoWay}"/>
<TextBlock Text="{Binding DateTimeString}" Padding="10,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderSeverity}" Binding="{Binding SeverityString}" Width="auto" HeaderStyle="{StaticResource WithoutButt}"/>
<DataGridTextColumn Header="{x:Static Trans:TranslatedResources.AlarmColumnHeaderDescription}" Binding="{Binding Description}" d:Width="400" Width="*" HeaderStyle="{StaticResource WithoutButt}"/>
</DataGrid.Columns>
</DataGrid>
The AlarmList View Model
public class AlarmListViewModel : MainViewModelBase
{
#region Properties
private ObservableCollection<AlarmEntryViewModel> _alarms = new ObservableCollection<AlarmEntryViewModel>();
public ObservableCollection<AlarmEntryViewModel> Alarms
{
get { return _alarms; }
set
{
_alarms = value;
RaisePropertyChanged(() => Alarms);
}
}
private AlarmEntryViewModel _selectedAlarm;
public AlarmEntryViewModel SelectedAlarm
{
get { return _selectedAlarm; }
set
{
_selectedAlarm = value;
RaisePropertyChanged(() => SelectedAlarm);
}
}
private int _acknowledgeAllowed;
public int AcknowledgeAllowed
{
get { return _acknowledgeAllowed; }
set
{
_acknowledgeAllowed = value;
RaisePropertyChanged(() => AcknowledgeAllowed);
}
}
private readonly IActionCommand _acknowledgeCommand;
public IActionCommand AcknowledgeCommand
{
get { return _acknowledgeCommand; }
}
public int MaxAcknowledgedAlarm;
public double RowHeight { get; set; }
public AlarmEntryViewModel AlarmToSend { get; set; }
#endregion
#region Constructor
public AlarmListViewModel()
{
if (!IsInDesignMode)
{
RowHeight = 30;
}
//Add command
_acknowledgeCommand = new ActionCommand<double>(p => Acknowledge(p));
}
#endregion
#region Private Methods
private void Acknowledge(double parameter)
{
if (AcknowledgeAllowed == 1)
{
try
{
//Get Datagrid width
double DatagridWidth = (double)parameter;
//Calculate the amount of fully visible alarms
int AmountVisible = (int)Math.Floor(DatagridWidth / RowHeight);
if (Alarms.Count < AmountVisible)
{
AlarmToSend = Alarms[Alarms.Count - 1];
foreach(AlarmEntryViewModel alarm in Alarms)
{
alarm.AckStatus = true;
alarm.Description = "Iets nieuw";
}
}
else
{
//Send the last visible alarm, 0 based => -1
AlarmToSend = Alarms[AmountVisible - 1];
//Go to next visible alarm
SelectedAlarm = Alarms[AmountVisible];
}
//Acknowledge alarms
_proxy.Send(AlarmToSend.AlarmNumber);
}
catch (Exception ex)
{
_viewManager.ShowDialog(new MessageDialogViewModel()
{
AskAnswer = false,
Text = ex.Message,
Title = TranslatedResources.AlarmAckSendErrorTitle,
});
}
}
else
{
_viewManager.ShowDialog(new MessageDialogViewModel()
{
AskAnswer = false,
Text = TranslatedResources.AlarmAcknErrorMessage,
Title = TranslatedResources.AlarmAcknErrorTitle,
});
}
}
#endregion
The AlarmListEntry view model
public class AlarmEntryViewModel : MainViewModelBase
{
#region Fields
private readonly IViewManager _viewManager;
private readonly IDockManager _dockManager;
private string _description;
#endregion
#region Constructor
public AlarmEntryViewModel()
{
}
#endregion
#region Model & Proxy
private Alarm _model;
public Alarm Model
{
set { _model = value; }
}
public AlarmListServiceProxy Proxy { get; set; }
#endregion
#region Properties
public DateTime Time { get { return _model.Time; } }
public string DateTimeString { get { return _model.Time.ToString("dd/MM/yyyy hh:mm:ss"); } }
public int Severity { get { return _model.Severity; } }
public string SeverityString
{
get
{
if (_model.Severity == 0)
return "Melding";
if (_model.Severity == 1)
return "Waarschuwing";
if (_model.Severity == 2)
return "Fout";
else
return "Niet gekend";
}
}
public string Description
{
get
{
//string substring = _model.Description.Substring(_model.Description.Length - 1);
//if ( substring.Equals("\n"))
//{
// return _model.Description.Substring(0, _model.Description.Length - 1);
//}
//else
// return _model.Description;
return _description;
}
set
{
_description = value;
RaisePropertyChanged(() => Description);
}
}
public int AlarmNumber { get { return _model.Number; } }
public int AcknStatus { get { return _model.AcknStatus; } }
private bool _ackStatus;
public bool AckStatus
{
get
{
if (_model.AcknStatus == 0)
return false;
else
return true;
}
set
{
_ackStatus = value;
RaisePropertyChanged(() => AckStatus);
}
}
#endregion
}
You set _ackStatus in the setter but doesn't return the value of this field from the getter in AlarmEntryViewModel. Do this:
private bool _ackStatus;
public bool AckStatus
{
get
{
return _ackStatus;
}
set
{
_ackStatus = value;
RaisePropertyChanged(() => AckStatus);
}
}
Or set the _model.AcknStatus field in the setter:
private bool _ackStatus;
public bool AckStatus
{
get
{
if (_model.AcknStatus == 0)
return false;
else
return true;
}
set
{
_ackStatus = value;
_model.AcknStatus = value ? 1 : 0;
RaisePropertyChanged(() => AckStatus);
}
}

DataGrid not updating with ObservableCollection

I have implemented a DataGrid as follows:
<DataGrid Name="grdSignals" Grid.Column="1" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Signal Name" Binding="{Binding SignalName}"/>
<DataGridTextColumn Header="Value" Binding="{Binding SignalValue}" />
</DataGrid.Columns>
</DataGrid>
The following is the Class where I implemented the properties used in an ObservableCollection:
namespace Test
{
public partial class MyMain : UserControl
{
public ObservableCollection<Signals> ocSignalNames;
Signals time, mode;
public MyMain()
{
InitializeComponent();
ocSignalNames = new ObservableCollection<Signals>();
time = new Signals() { SignalName = "Time", SignalValue = "" };
mode = new Signals() { SignalName = "Mode", SignalValue = "" };
ocSignalNames.Add(time);
ocSignalNames.Add(mode);
grdSignals.DataContext = ocSignalNames;
}
}
public class Signals : INotifyPropertyChanged
{
string _signalName, _signalValue;
public string SignalName
{
get
{
return _signalName;
}
set
{
_signalName = value;
}
}
public string SignalValue
{
get
{
return _signalValue;
}
set
{
_signalValue = value;
OnPropertyChanged(SignalValue);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
After debugging it, I found that the ocSignalNames is getting updated when its items change. But the changes are not getting reflected in the DataGrid. I am updating the Items in a thread like this:
time.SignalValue = iTest1.ToString();
mode.SignalValue = iTest2.ToString();
What am I missing out?
You are missing the correct NotifyChanged handlers:
public string SignalName
{
get
{
return _signalName;
}
set
{
_signalName = value;
OnPropertyChanged("SignalName"); //Added
}
}
public string SignalValue
{
get
{
return _signalValue;
}
set
{
_signalValue = value;
OnPropertyChanged("SignalValue"); //NOTE: quotation marks added
}
}
you have to use below mentioned code.
string _signalName, _signalValue;
public string SignalName
{
get
{
return _signalName;
}
set
{
_signalName = value;
OnPropertyChanged("SignalName");
}
}
public string SignalValue
{
get
{
return _signalValue;
}
set
{
_signalValue = value;
OnPropertyChanged(SignalValue);
}
}
your XAML look like
<DataGrid Name="grdSignals" Grid.Column="1" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Signal Name" Binding="{Binding SignalName, Mode=TwoWay}"/>
<DataGridTextColumn Header="Value" Binding="{Binding SignalValue, Mode=TwoWay}" />
</DataGrid.Columns>
</DataGrid>
O/P

wpf datagrid :list all errors when save click

I am creating a WPF application that will use IDataErrorInfo data validation, which my business objects implement. Now I want to list all the validation errors to the user in a messagebox when the user clicks the save button. How to acheive this?
My Datagrid is:
<my:DataGrid Name="dgReceiveInventory" RowStyle="{StaticResource RowStyle}" ItemsSource="{Binding}" GotFocus="dgReceiveInventory_GotFocus" CanUserDeleteRows="False" CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserResizeRows="False" CanUserSortColumns="False" RowHeight="23" SelectionUnit="Cell" AutoGenerateColumns="False" Margin="12,84,10,52" BeginningEdit="dgReceiveInventory_BeginningEdit">
<my:DataGrid.Columns>
<!--0-Product Column-->
<my:DataGridTemplateColumn Header="Product Name" Width="200">
<my:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource TextBlockInError}" Text="{Binding ProductName,ValidatesOnDataErrors=True}" ></TextBlock>
</DataTemplate>
</my:DataGridTemplateColumn.CellTemplate>
<my:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox x:Name="txtbxProduct" Text="{Binding Path=ProductName,UpdateSourceTrigger=LostFocus,ValidatesOnDataErrors=True}" TextChanged="txtbxProduct_TextChanged" PreviewKeyDown="txtbxProduct_PreviewKeyDown" >
</TextBox>
</DataTemplate>
</my:DataGridTemplateColumn.CellEditingTemplate>
</my:DataGridTemplateColumn>
<!--1-Purchase Rate Column-->
<my:DataGridTextColumn Header="Purchase Rate" Width="100" Binding="{Binding PurchaseRate}" IsReadOnly="True"></my:DataGridTextColumn>
<!--2-Avaialable Qty Column-->
<my:DataGridTextColumn Header="Stock" Binding="{Binding AvailableQty}" IsReadOnly="True" Visibility="Hidden"></my:DataGridTextColumn>
<!--4-Amount Column-->
<my:DataGridTextColumn Header="Amount" Width="100" Binding="{Binding Amount}" ></my:DataGridTextColumn>
</my:DataGrid.Columns>
</my:DataGrid>
My Object is:
class clsProducts : INotifyPropertyChanged, IDataErrorInfo
{
private string _ProductName;
private decimal _PurchaseRate;
private int _AvailableQty;
private int _Qty;
private decimal _Amount;
#region Property Getters and Setters
public string ProductName
{
get { return _ProductName; }
set
{
if (_ProductName != value)
{
_ProductName = value;
OnPropertyChanged("ProductName");
}
}
}
public decimal PurchaseRate
{
get { return _PurchaseRate; }
set
{
_PurchaseRate = value;
OnPropertyChanged("PurchaseRate");
}
}
public int AvailableQty
{
get { return _AvailableQty; }
set
{
_AvailableQty = value;
OnPropertyChanged("AvailableQty");
}
}
public int Qty
{
get { return _Qty; }
set
{
_Qty = value;
this._Amount = this._Qty * this._PurchaseRate;
OnPropertyChanged("Qty");
OnPropertyChanged("Amount");
}
}
public decimal Amount
{
get { return _Amount; }
set
{
_Amount = value;
OnPropertyChanged("Amount");
}
}
#endregion
#region IDataErrorInfo Members
public string Error
{
get
{
return "";
}
}
public string this[string name]
{
get
{
string result = null;
if (name == "ProductName")
{
if (this._ProductName != null)
{
int count = Global.ItemExist(this._ProductName);
if (count == 0)
{
result = "Invalid Product";
}
}
}
else if (name == "Qty")
{
if (this._Qty > this._AvailableQty)
{
result = "Qty must be less than Available Qty . Avaialble Qty : " + this._AvailableQty;
}
}
return result;
}
}
#endregion
#region INotifyPropertyChanged Members
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
//// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
I don't clear understand why you want to do so but as example you can enumerate rows and call Validate method on your own like this:
private void Save_Click(object sender, RoutedEventArgs e) {
// create validation object
RowDataInfoValidationRule rule = new RowDataInfoValidationRule();
StringBuilder builder = new StringBuilder();
// enumerate all rows
for (int i = 0; i < dgReceiveInventory.Items.Count; i++) {
DataGridRow row = (DataGridRow) dgReceiveInventory.ItemContainerGenerator.ContainerFromIndex(i);
// validate rule
ValidationResult res = rule.Validate(row.BindingGroup, null);
if (!res.IsValid) {
// collect errors
builder.Append(res.ErrorContent);
}
}
//show message box
MessageBox.Show(builder.ToString());
}
If you have
<DataGrid>
<DataGrid.RowValidationRules>
<local:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
</DataGrid.RowValidationRules>
...
You could use Validation.Error Attached Event
<Window Validation.Error="Window_Error">
to save all validation errors for bindings with NotifyOnValidationError set to true
Text="{Binding ProductName, ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
in a List
public List<ValidationError> ValidationErrors = new List<ValidationError>();
private void Window_Error(object sender, ValidationErrorEventArgs e)
{
if (e.Action == ValidationErrorEventAction.Added)
ValidationErrors.Add(e.Error);
else
ValidationErrors.Remove(e.Error);
}
and then show the list entries in a MessageBox in save button click handler.

Datagrid selectedItem and databinding

I have two datagrid displayed on my UI. When I select a particular row on datagrid 1, I would like to display the details of the datagrid 1 on datagrid 2. I am populating the datagrid data from a database.
here is the two database table structure.
Note: both the table are mapped by the personid in the database
here is the code so far I have tried
Baseclass.cs
public class Baseclass
{
public event PropertyChangedEventHandler PropertyChanged;
protected void SetProperty<T>(ref T member, T value, [CallerMemberName] string propertyName = null)
{
member = value;
this.RaiseNotification(propertyName);
}
protected void RaiseNotification(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
person.cs
public class person : Baseclass
{
private int personID;
public int PersonID
{
get { return personID; }
set { this.SetProperty<int>(ref this.personID, value); }
}
private string firstName;
public string FirstName
{
get { return firstName; }
set { this.SetProperty<string>(ref this.firstName, value); }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { this.SetProperty<string>(ref this.lastName, value); }
}
Model _personModel = new Model();
private ObservableCollection<person> _person = new ObservableCollection<person>();
public ObservableCollection<person> Getpersons
{
get { return _person; }
set { _person = value; OnPropertyChanged("GetPersons"); }
}
public person()
{
initializeload();
}
private void initializeload()
{
try
{
DataTable table = _personModel.getData();
for (int i = 0; i < table.Rows.Count; ++i)
Getpersons.Add(new person
{
PersonID = Convert.ToInt32(table.Rows[i][0]),
FirstName = table.Rows[i][1].ToString(),
LastName = table.Rows[i][2].ToString(),
});
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
public class Model
{
public DataTable getData()
{
DataTable ndt = new DataTable();
SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
sqlcon.Open();
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [Person].[dbo].[persons]", sqlcon);
da.Fill(ndt);
da.Dispose();
sqlcon.Close();
return ndt;
}
}
}
PersonDetail class
public class PersonDetails : Baseclass
{
private int personID;
public int PersonID
{
get { return personID; }
set { this.SetProperty<int>(ref this.personID, value); }
}
private string address;
public string Address
{
get { return address; }
set { this.SetProperty<string>(ref this.address, value); }
}
private string pos;
public string Position
{
get { return pos; }
set { this.SetProperty<string>(ref this.pos, value); }
}
DetailsModel _detailModel = new DetailsModel();
private ObservableCollection<PersonDetails> _details = new ObservableCollection<PersonDetails>();
public ObservableCollection<PersonDetails> GetDetails
{
get { return _details; }
set { _details = value; OnPropertyChanged("GetDetails"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
public PersonDetails()
{
initializeload();
}
private void initializeload()
{
try
{
DataTable table = _detailModel.getData();
for (int i = 0; i < table.Rows.Count; ++i)
GetDetails.Add(new PersonDetails
{
PersonID = Convert.ToInt32(table.Rows[i][0]),
Address = table.Rows[i][1].ToString(),
Position = table.Rows[i][2].ToString(),
});
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public class DetailsModel
{
public DataTable getData()
{
DataTable ndt = new DataTable();
SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["MyConnectionString"].ConnectionString);
sqlcon.Open();
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM [Person].[dbo].[personDetails]", sqlcon);
da.Fill(ndt);
da.Dispose();
sqlcon.Close();
return ndt;
}
}
}
MainViewModel.cs
public class MainViewModel : Base, INotifyPropertyChanged
{
public MainViewModel()
{
}
private ObservableCollection<person> personValues;
public ObservableCollection<person> Persons
{
get { return personValues; }
set
{
this.SetProperty<ObservableCollection<person>>(ref this.personValues, value);
}
}
private ObservableCollection<PersonDetails> detailsValues;
public ObservableCollection<PersonDetails> Details
{
/* This is correct below ?? I have an error as
'PersonDemo.MainViewModel' does not contain a definition for 'GetDetails' and no extension method 'GetDetails' accepting a first argument of type 'PersonDemo.MainViewModel' could be found (are you missing a using directive or an assembly reference?)*/
get { return this.GetDetails(this.Selectedperson.PersonID); }
}
private person selectedValue;
public person Selectedperson
{
get { return selectedValue; }
set
{
this.SetProperty<person>(ref this.selectedValue, value);
this.RaiseNotification("Details");
}
}
}
XAML
<Grid>
<DataGrid Margin="100,20,116,211" ItemsSource="{Binding Persons}" SelectedItem="{Binding Selectedperson}" />
<DataGrid Margin="100,130,116,101" ItemsSource="{Binding Details}" />
</Grid>
can anybody help to proceed in writing the MainViewModel? I am stuck here since weeks.
First, I recomend to use the INotifyPropertyChanged interface in the base class. Maybe you use it and forget write it in the sample code. For make this with MVVM patter, you need to implement a ViewModel for both data grids. Let's call it in our example BothGridViewModel, you may call it as you want. Then in this view model you need the collection of all person, let's call it AllPerson, then you need to have a property of Person type, when you will have the selected person in the grid, let's call it SelectedPerson. It will be something like this:
public class BothGridViewModel:INotifyPropertyChanged
{
...
public ObservableCollection<Person> AllPersons {...}
public Person SelectedPerson {...}
...
}
All you need, is to set it in the DataContext of the View. And bindings:
<YourView DataContext={Binding SomeBothGridViewModelClass}>
<Grid>
<DataGrid Margin="100,20,116,211" ItemsSource="{Binding AllPersons}" SelectedItem="{Binding SelectedPerson}" />
<DataGrid Margin="100,130,116,101" ItemsSource="{Binding SelectedPerson.Getpersons}" /> <!--Or some SelectedPerson.PersonDetails.GetDetails-->
</Grid>
</YourView DataContext={Binding SomeBothGridViewModelClass}>
I think this is the good view model structure for make what you want. Hope this helps you...
EDIT
I see your point now, you have two database tables, one for the main properties and other for the details. I see two good ways for doing this:
1) The first one, is that I don't belive that the second datagrid is necesary due for each person you don't have a collection of details. Instead you may use a grid, and other controls for showing the properties. Also I think you must to implement a view model for the person, for instance:
public class PersonViewModel:INotifyPropertyChanged
{
public string FirstName {...}
public string LastName {...}
//Other properties
public PersonDetails Details {...}
}
Then in the grid, you may bind the items source to a collection of PersonViewModel then you can make bindings to the selected item of the grid, for instance:
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource={Binding AllPersonViewModels}/>
...
<!--Then some combo box, text block or text box binding to a property of the selected item's details-->
...
<TextBox Text={Binding SelectedItem.Details.Address, ElementName=dataGrid}/>
...
<Grid>
2) The second way I think could be done, is showing all data in the same datagrid. For this you need to do the PersonViewModel class in this way:
public class PersonViewModel:INotifyPropertyChanged
{
public string FirstName {...}
public string LastName {...}
//Other properties
//Then the Details properties
public string Address {...}
//...
//Note this class is like a wrapper for person, and person details
}
This way is a bit simpler, but maybe cause an unwanted database over access.
EDIT 2
After have a look of your code, I have to say a few things about it: In the PersonViewModel and PersonDetailViewModel you should use a DispatcherTimer instead a Timer, because we are in Wpf. Other thing, maybe you should use a Repository pattern, to create a PersonRepository and a PersonDetailRepository where to put all the DB comunication, in fact, PersonViewModel and PersonDetailViewModel are a in some way, repositories, but by now you don't need to change it, it should work. I'm going to show you here the code of the MainViewModel, I nodified it to have the SelectedPersonDetail property, in this way, all you need to do is make a binding in the View:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using tryout13022013.PersonViewModels;
using System.ComponentModel;
using tryout13022013.DetailsViewModel;
using tryout13022013.PersonModel;
namespace tryout13022013
{
public class MainViewModel
{
private PersonViewModel _subPerson = new PersonViewModel();
public PersonViewModel SubPerson
{
get
{
return _subPerson;
}
set
{
if (_subPerson != value)
{
_subPerson = value; OnPropertyChanged("SubPerson");
}
}
}
private PersonDetailsViewModel _subDetail = new PersonDetailsViewModel();
public PersonDetailsViewModel SubDetail
{
get { return _subDetail; }
set
{
_subDetail = value; OnPropertyChanged("SubDetail");
}
}
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set {
if (_selectedPerson != value)
{
_selectedPerson = value;
OnPropertyChanged("SelectedPerson");
OnPropertyChanged("SelectedPersonDetail"); //In this way when Change the Selected Person, the Selected Detail will be changed again...
//if (this.SelectedPerson != null && this.SubDetail != null)
//{
// I dont know how to call the PersonDetailsViewModel class like a method here in order to load its data. kindly help
//this.SubDetail.MethodforLoadingPersonDetails(this.SelectedPerson);
//}
}
}
}
public PersonDetails SelectedPersonDetail
{
get
{
if (SubDetail == null || SelectedPerson ==null)
return null;
return SubDetails.DetailsData.FirstOrDefault(detail => detail.PersonID == SelectedPerson.PersonID);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyname)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyname));
}
}
}
And this is an instace of a binding you can make in your View, in this case for selecting the item in the second grid:
<Window x:Class="tryout13022013.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:person="clr-namespace:tryout13022013.PersonViewModels"
xmlns:details="clr-namespace:tryout13022013.DetailsViewModel"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid ItemsSource="{Binding SubPerson.PersonData}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"
AutoGenerateColumns="False" Height="77" HorizontalAlignment="Left"
Margin="101,26,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="auto" >
<DataGrid.DataContext>
<person:PersonViewModel/>
</DataGrid.DataContext>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="auto" Binding="{Binding PersonID}"/>
<DataGridTextColumn Header="First Name" Width="auto" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Width="auto" Binding="{Binding LastName}"/>
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource="{Binding SubDetail.DetailsData}"
AutoGenerateColumns="False" Height="77" HorizontalAlignment="Left"
Margin="101,135,0,0" Name="dataGrid2" VerticalAlignment="Top" Width="255" SelectedItem="{Binding SelectedPersonDetail, Mode=OneWayToSource}">
<DataGrid.DataContext>
<details:PersonDetailsViewModel/>
</DataGrid.DataContext>
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Width="auto" Binding="{Binding PersonID}"/>
<DataGridTextColumn Header="Address" Width="auto" Binding="{Binding Address}"/>
<DataGridTextColumn Header="Position" Width="auto" Binding="{Binding Description}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
I forgot it, you need to make the binding to SubPerson.PersonData and SubDetail.DetailsData, these are the collection.
Check it out...
Having had a chat with Buba1947 in WPF room, I've concluded that she's actually got a 1-1 relation split table in database that she's actually trying to show as such in the split data grids.
I've proposed her to create a PersonViewModel on top of Person & PersonDetails tables so her MainViewModel contains only one ObservableCollection<PersonViewModel> under the Persons property to which we shall bind the two DataGrids.
so MainViewModel.cs (the DataContext) has:
private ObservableCollection<PersonViewModel> personValues;
public ObservableCollection<PersonViewModel> Persons
{
get { return personValues; }
set { this.SetProperty<ObservableCollection<PersonViewModel>>(ref this.personValues, value); }
}
The data adaptor is changed to bring the data in using an inner join with relevant query being something along the lines of this (which fills the Persons property above):
"SELECT * FROM [Person].[dbo].[persons] INNER JOIN [Person].[dbo].[personDetails] ON [Person].[dbo].[persons].[Id] = [Person].[dbo].[personDetails].[Id]"
Next we shall bind a CollectionViewSource to Persons in Xaml:
<Window.Resources>
<CollectionViewSource x:Key="PersonsData" Source={Binding Persons} />
</Window.Resources>
and bind DataGrids to this with individual columns:
<DataGrid ItemsSource={Binding Source={StaticResource PersonsData}} AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Content={Binding PersonId} />
<DataGridTextColumn Header="First Name" Content={Binding FirstName} />
<DataGridTextColumn Header="Last Name" Content={Binding LastName} />
</DataGrid.Columns>
</DataGrid>
<DataGrid ItemsSource={Binding Source={StaticResource PersonsData}} AutoGenerateColumns="false">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Content={Binding PersonId} />
<DataGridTextColumn Header="Address" Content={Binding Address} />
<DataGridTextColumn Header="Position" Content={Binding Position} />
</DataGrid.Columns>
</DataGrid>
Note: there may be typo in here as I haven't got VS to hand, please let me know if I need to fix something. I've written all of this from memory.

Validating bound ObservableCollection in ViewModel using MVVM Pattern

I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.

Categories

Resources