Bound observable collection update without 'RemoveOf' & 'Insert' - c#

Currently I am trying to implement an observable collection which is bound to a data template (WPF-MVVM). During initialization it loads the default value to observable collection. Idea is:
User provides some value on the textbox,
presses ENTER key
increments a counter and updates the count value on textblock which is located near the text box.
The purpose is to track how times the text value has been changed by the user.
Right now it is working with 'IndexOf', 'RemoveAt' and 'Insert'. Is there a way to do without 'RemoveAt' and 'Insert'.
I feel something wrong on my code? Can anybody help it.
InputDataTemplate.xaml:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Name}" />
<Label Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Count}" />
<TextBox x:Name="IpDataTb" Grid.Column="1" Width="60" HorizontalAlignment="Center" VerticalAlignment="Center" DataContext="{Binding}" Text="{Binding Path=Data, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<ei:CallMethodAction TargetObject="{Binding }" MethodName="IpDataTrig" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
TestView.xaml:
<UserControl.Resources>
<DataTemplate x:Key="InputDataTemplate" >
<local:InputDataTemplate DataContext="{Binding}" />
</DataTemplate>
</UserControl.Resources>
<Grid>
<Border BorderBrush="#FF0254B4" BorderThickness="1" >
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" >
<ItemsControl ItemsSource="{Binding InputDatas}"
ItemTemplate="{DynamicResource InputDataTemplate}" />
</ScrollViewer>
</Border>
</Grid>
DataService.cs:
using MyObsrCollTest.ViewModels;
namespace MyObsrCollTest.Services
{
public class InputDataService : BindableBase
{
public string Name { get; set; }
public string Count { get; set; }
public string Data { get; set; }
public void IpDataTrig(object sender,KeyEventArgs e)
{
var IpDataTb = new TextBox();
IpDataTb = (TextBox)sender;
if ((e.Key == Key.Enter) &&(!string.IsNullOrWhiteSpace(IpDataTb.Text)))
{
this.Data = IpDataTb.Text;
ObsrCollTestVm.TestMe(this.Name, this.Data);
}
}
}
}
ObsrCollTestVm.cs:
private ObservableCollection<InputDataService> _InputDatas;
static int _count = 0;
public ObsrCollTestVm(void)
{
for (int i = 0; i < 5; i++)
{
var l_InputDatas = new InputDataService();
l_InputDatas.Name = i.ToString();
l_InputDatas.Count = "0";
l_InputDatas.Data = "?";
_InputDatas.Add(l_InputDatas);
}
}
Basic initialization routine:
public ObservableCollection<InputDataService> InputDatas
{
get
{
if (_InputDatas == null)
{
_InputDatas = new ObservableCollection<InputDataService>();
}
return _InputDatas;
}
}
New Observable collection:
public static void TestMe(string name, string data)
{
var found = _InputDatas.FirstOrDefault(element = > element.Name == name);
if (found != null)
{
int i = _InputDatas.IndexOf(found);
found.Count = _count++;
_InputDatas.RemoveAt(i);
_InputDatas.Insert(i, found);
}
}
Increment the count value:

If I understand the question correctly, it can be summarized as:
"I would like to be able to change the Count property of my InputDataService class objects and have that change reflected in that item's Label, without having to modify the ObservableCollection<InputDataService> itself."
Is that correct?
If so, then the solution is for your InputDataService class to correctly provide notifications of property changes. Normally, this would mean either inheriting DependencyObject and implementing your properties as dependency properties, or just implementing the INotifyPropertyChanged interface.
But in your example, you seem to be inheriting a class named BindableBase already. If that class is in fact the Microsoft.Practices.Prism.Mvvm.BindableBase class, then it already implements INotifyPropertyChanged and all you need to do is take advantage of that.
For example:
public class InputDataService : BindableBase
{
private int _count;
public string Name { get; set; }
public int Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
public string Data { get; set; }
public void IpDataTrig(object sender,KeyEventArgs e)
{
var IpDataTb = new TextBox();
IpDataTb = (TextBox)sender;
if ((e.Key == Key.Enter) &&(!string.IsNullOrWhiteSpace(IpDataTb.Text)))
{
this.Data = IpDataTb.Text;
ObsrCollTestVm.TestMe(this.Name, this.Data);
}
}
}
Notes:
In the above, I only fixed the issue for the Count property. You can apply similar changes to the other properties to get them to update correctly.
In your TestMe() method, you seem to be using the Count property as an int, but it was declared in your code example as a string. Lacking a better way to reconcile that discrepancy in your code example, I've just changed the property declaration in the example above to use int instead of string.
This example assumes you are using .NET 4.5, in which the [CallerMemberName] attribute is supported. If you're using an earlier version of .NET, then you will need to add the property name to the SetProperty() call. E.g.: SetProperty(ref _count, value, "Count");
With these changes, you should be able to write TestMe() like this:
public static void TestMe(string name, string data)
{
var found = _InputDatas.FirstOrDefault(element = > element.Name == name);
if (found != null)
{
found.Count = _count++;
}
}

Related

Edit Record. Pass values of bound TextBoxes to View Model

I have a DataGrid which is bound to a ViewModel. When I select a record from the DataGrid, the TextBoxes (Username and Role) are displaying the data from the selected record.
I want to edit the selected record but I'd like to check the data before it updates the list, hence the 'OneWay' binding mode.
I'm having trouble passing the values of the textboxes to the view model. I can get a value of one textboxes through the button and passing the value to my ICommand
<Button Grid.Row="5" Grid.Column="1" Content="Edit" Margin="5 5"
Command="{Binding EditUserCmd, Source={StaticResource viewModelUsers}}" CommandParameter="{Binding Text, ElementName=txtUsername}
Is there a way to pass all the textboxes to the view model by creating a property in it that holds selected user? or passing the values of the texboxes to the view model somehow??
Thanks.
My view model
public class UsersViewModel
{
public ObservableCollection<UsersModel> Users { get; set; }
private ICommand addUserCommand;
private ICommand removeUserCommand;
private ICommand editUserCommand;
public ICommand AddUserCmd => addUserCommand ?? (addUserCommand = new AddUserCommand(this));
public ICommand RemoveUserCmd => removeUserCommand ?? (removeUserCommand = new DeleteUserCommand(this));
public ICommand EditUserCmd => editUserCommand ?? (editUserCommand = new EditUserCommand(this));
private UsersModel selectedUser = new UsersModel();
public UsersModel SelectedUser
{
get { return this.selectedUser; }
set
{
this.selectedUser = value;
}
}
public UsersViewModel()
{
// fetch data from db.
DataAccess da = new DataAccess();
Users = new ObservableCollection<UsersModel>(da.GetRegisteredUsers());
}
}
Model
public class UsersModel
{
public int Id { get; set; }
public string Username { get; set; }
public string Surname {get; set;}
}
Edit Command
internal class EditUserCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public UsersViewModel UsersViewModel { get; set; }
public EditUserCommand(UsersViewModel usersViewModel)
{
this.UsersViewModel = usersViewModel;
}
public bool CanExecute(object parameter)
{
// UsersModel user = (UsersModel)parameter;
// if (user != null)
//return !string.IsNullOrEmpty(user.Id.ToString());
return true;
}
public void Execute(object parameter)
{
// UsersModel user = (UsersModel)parameter;
// if (user != null)
// this.UsersViewModel.Users
}
}
xaml
...
<Window.Resources>[enter image description here][1]
<m:UsersModel x:Key="users"></m:UsersModel>
<vm:UsersViewModel x:Key="viewModelUsers"/>
</Windows.Resources>
...
<DataGrid x:Name="gridUsers"
Grid.Row="0"
DataContext="{Binding Source={StaticResource viewModelUsers}}" CanUserAddRows="False"
ItemsSource="{Binding Users}">
</DataGrid>
<Grid Margin="10" Grid.Row="1" DataContext="{Binding ElementName=gridUsers, Path=SelectedItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0">UserName:</Label>
<TextBox x:Name="txtUsername" Grid.Row="0" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Path=Username, Mode=OneWay}"/>
<Label Grid.Row="1">Role:</Label>
<TextBox x:Name="txtRole" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10" Text="{Binding Path=Role, Mode=OneWay}"/>
<StackPanel Grid.Row="5" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center">
<Button Grid.Row="5" Grid.Column="1" Content="Edit" Margin="5 5"
Command="{Binding EditUserCmd, Source={StaticResource viewModelUsers}}" CommandParameter="{Binding Text, ElementName=txtUsername}">
</StackPanel>
</Grid>
Your ViewModel should not know about text boxes - just add two new properties ([PropertyName]EditValue) and bind to them, then in your command check them and copy them to the model if correct or restore them if incorrect - This is the entire point in using view models instead of binding to models directly
Did you know that you can edit the DataGrid cells directly? You can even use data validation. This way invalid data cells get a red border and the data won't be committed unless the validation passes.
Another option is to let UsersModel implement INotifyDataErrorInfo and validate properties directly. Then bind the DataGrid.SelectedItem to the view model and bind the edit TextBox elements to this property. This way you implemented live update and got rid of the edit commands:
UsersViewModel.cs
public class UsersViewModel
{
public ObservableCollection<UsersModel> Users { get; set; }
private UsersModel selectedUser;
public UsersModel SelectedUser
{
get => this.selectedUser;
set => this.selectedUser = value;
}
public UsersViewModel()
{
// fetch data from db.
DataAccess da = new DataAccess();
Users = new ObservableCollection<UsersModel>(da.GetRegisteredUsers());
}
}
UsersModel.cs
public class UsersModel : INotifyDataErrorInfo
{
private int id;
public int Id
{
get => this.id;
set { if (this.id != value && IsIdValid(value)) this.id = value; }
}
private string userName;
public string UserName
{
get => this.userName;
set { if (this.userName != value && IsUserNameValid(value) && ) this.userName = value; }
}
private string surname;
public string Surname
{
get => this.surname;
set { if (this.surname != value && IsSurnameValid(value) && ) this.surname = value; }
}
// Validates the Id property, updating the errors collection as needed.
public bool IsIdValid(int value)
{
RemoveError(nameof(this.Id), ID_ERROR);
if (value < 0)
{
AddError(nameof(this.Id), ID_ERROR, false);
return false;
}
return true;
}
public bool IsUserNameValid(string value)
{
RemoveError(nameof(this.UserName), USER_NAME_ERROR);
if (string.IsNullOrWhiteSpace(value))
{
AddError(nameof(this.UserName), USER_NAME_ERROR, false);
return false;
}
return true;
}
public bool IsSurnameValid(string value)
{
RemoveError(nameof(this.Surname), SURNAME_ERROR);
if (string.IsNullOrWhiteSpace(value))
{
AddError(nameof(this.Surname), SURNAME_ERROR, false);
return false;
}
return true;
}
private Dictionary<String, List<String>> errors =
new Dictionary<string, List<string>>();
private const string ID_ERROR = "Value cannot be less than 0.";
private const string USER_NAME_ERROR = "Value cannot be empty.";
private const string SURNAME_ERROR = "Value cannot be empty.";
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if isWarning is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!errors.ContainsKey(propertyName))
errors[propertyName] = new List<string>();
if (!errors[propertyName].Contains(error))
{
if (isWarning) errors[propertyName].Add(error);
else errors[propertyName].Insert(0, error);
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (errors.ContainsKey(propertyName) &&
errors[propertyName].Contains(error))
{
errors[propertyName].Remove(error);
if (errors[propertyName].Count == 0) errors.Remove(propertyName);
RaiseErrorsChanged(propertyName);
}
}
public void RaiseErrorsChanged(string propertyName)
{
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!errors.ContainsKey(propertyName)) return null;
return errors[propertyName];
}
public bool HasErrors
{
get => errors.Count > 0;
}
#endregion
}
View
TextBox.Text bindings must be set to TwoWay (which is the default Binding.Mode value for this property)
<DataGrid x:Name="gridUsers"
DataContext="{Binding Source={StaticResource viewModelUsers}}"
CanUserAddRows="False"
ItemsSource="{Binding Users}"
SelectedItem="{Binding SelectedUser"} />
<Grid DataContext="{Binding Source={StaticResource viewModelUsers}, Path=SelectedUser}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Label Grid.Row="0">UserName:</Label>
<TextBox x:Name="txtUsername" Grid.Row="0" Grid.Column="1"
Text="{Binding Username, NotifyOnValidationError=True"/>
<Label Grid.Row="1">Role:</Label>
<TextBox x:Name="txtRole" Grid.Row="1" Grid.Column="1"
Text="{Binding Role, NotifyOnValidationError=True}"/>
</Grid>

Data Binding does not Update on Property Change (UWP)

I am developing an application using c# and the Universal Windows Platform (UWP) and am struggling with creating a one-way data-bind between a layout control and an observable class. Currently, when the observable class property is changed, it does not update the UI element. I think it has something to do with the fact that I am binding a DataTemplate ListViewItem rather than a static layout element, but I am not sure if this is the problem or how to solve it. Any help would be appreciated. The code for the UI element and backend code is shown.
DataTemplate (XAML) (Styling is removed for readability)
<DataTemplate x:Key="variableTemplate"
x:DataType="local:VariableNode">
<Border>
<StackPanel Orientation="Vertical">
<Border>
<Grid>
<TextBlock Text="{Binding Name}" />
<StackPanel Orientation="Horizontal" >
<Button Tag="{Binding Description}"/>
<Button Tag="{Binding}"/>
</StackPanel>
</Grid>
</Border>
<Grid Margin="0, 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border >
<Grid Grid.Column="0">
<Button Click="Choose_Measurement"
Tag="{Binding}">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Measurement_Name, Mode=TwoWay}"
Foreground="{x:Bind MF}" />
<TextBlock Foreground="{x:Bind MF}" />
</StackPanel>
</Button>
</Grid>
</Border>
<Grid Grid.Column="1">
<Button Foreground="{Binding UF}"
Tag="{Binding}"
IsEnabled="{Binding Unit_Exists}"
Click="Choose_Unit">
<StackPanel Orientation="Vertical">
<TextBlock Text="{x:Bind Path=Unit_Name, Mode=OneWay}"
Foreground="{Binding UF}" />
<TextBlock Foreground="{Binding UF}" />
</StackPanel>
</Button>
</Grid>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
C# Observable Class VariableNode (Irrelevant properties removed)
public class VariableNode : ExperimentNode
{
public VariableNode() { }
public VariableNode(VariableType type)
{
Type = type;
Name = name_ref[(int)Type];
Category = "Problem";
Unit = -1;
}
private string[] name_ref = { "Independent Variable", "Dependent Variable", "Controlled Variable" };
public enum VariableType { Independent, Dependent, Controlled };
public VariableType Type { get; set; }
public Measurement Measure { get; set; }
public int Unit { get; set; }
[XmlIgnoreAttribute]
public Measurement MeasureSource
{
get { return this.Measure; }
set
{
this.Measure = value;
OnPropertyChanged("Measurement_Name");
}
}
[XmlIgnoreAttribute]
public string Measurement_Name
{
get
{
if (Measure == null) { return "Select a Measurement"; }
else { return Measure.Name; }
}
set
{
if (Measure != null)
{
Measure.Name = value;
OnPropertyChanged();
}
}
}
[XmlIgnoreAttribute]
public string Unit_Name
{
get
{
if (Measure == null) { return "No measurement"; }
else if (Unit < 0) { return "Select a unit"; }
else { return Measure.Unit[Unit]; }
}
}
[XmlIgnoreAttribute]
public bool Unit_Exists
{
get { return Measure != null; }
}
}
C# XAML.CS code calling the property change
public void Choose_Measurement (object sender, RoutedEventArgs e)
{
Button butt = sender as Button
VariableNode sel = butt.Tag as VariableNode;
sel.Measurement_Name = "New Name";
}
Again thanks for the help, I know its a lot of code, and I appreciate the help in debugging / learning.
Ok, so I ended up finding the answer, and I think that it may help others trying to replicate what I am trying to do:
Basically, the class that one is trying to make observable must extend the class INotifyPropertyChanged. So, I ended up making a base class from which to extend all of my observable classes from:
public class BaseClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged(this, e);
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}

Databound list box not updating to correct values from observable collection WPF

I am new to WPF so could be something very simple I've missed.
I have a list box which is holding databound Class properties from a static observableCollection<myClass>. The collection is being updated several times a second from a network stream source and from what I can tell from debugging, the collection is being updated properly. The declaration is as follows: static ObservableCollection<PumpItem> pumpCollection = new ObservableCollection<PumpItem>(); where PumpItem is the name of my class.
That is not to say that the listbox isn't displaying anything however, it is updating to display any new values added to the collection but these only ever reflect the properties of the first moment they enter the collection.
The values of the listbox are bound as such:
<ListBox x:Name="pumpListBox" ItemsSource="{Binding PumpCollection}" Grid.IsSharedSizeScope="True" Margin="0,0,153,0">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="ID" />
<ColumnDefinition SharedSizeGroup="State" />
<ColumnDefinition SharedSizeGroup="Selection" />
<ColumnDefinition SharedSizeGroup="Fuel Pumped" />
<ColumnDefinition SharedSizeGroup="Cost" />
</Grid.ColumnDefinitions>
<TextBlock Margin="2" Text="{Binding PumpID}" Grid.Column="0"/>
<TextBlock Margin="2" Text="{Binding State}" Grid.Column="1"/>
<TextBlock Margin="2" Text="{Binding Selection}" Grid.Column="2"/>
<TextBlock Margin="2" Text="{Binding FuelPumped}" Grid.Column="3"/>
<TextBlock Margin="2" Text="{Binding FuelCost}" Grid.Column="4"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I have this declaration in the code behind in order to set resources to the collection:
public static ObservableCollection<PumpItem> PumpCollection
{
get { return pumpCollection; }
}
In my `MainWindow() constructor I've set:
this.DataContext = this;
before InitialiseComponent(); and my background worker thread to receive network inputs to update the list:worker.RunWorkerAsync();`
This background thread then loops continuously updates the collection from the stream and invokes a resource update:
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
//background tasks
Thread.Sleep(500); //allows the ui time to update and initialise
string s_decryptedMessage = string.Empty;
App.Current.Dispatcher.Invoke((Action)(() =>
{
Resources["PumpCollection"] = pumpCollection;
}));
while (true)
{
byteMessage = Recieve(stream);//get the number of pumps about to be recieved
Interact(byteMessage); //signal server to update pumplist
}
}
If it helps, my class code is thus:
namespace PoSClientWPF
{
public enum pumpState
{
Available,
Waiting,
Pumping,
Paying
};
public enum fuelSelection
{
Petrol,
Diesel,
LPG,
Hydrogen,
None
};
public class PumpItem
{
private string pumpID;
public string PumpID
{
get
{
return pumpID;
}
set
{
pumpID = value;
}
}
private double fuelPumped;
public double FuelPumped
{
get
{
return fuelPumped;
}
set
{
fuelPumped = value;
}
}
private double fuelCost;
public double FuelCost
{
get
{
return fuelCost;
}
set
{
fuelCost = Math.Round(value, 2); //cost to two DP
}
}
private fuelSelection selection;
public fuelSelection Selection
{
get
{
return selection;
}
set
{
selection = value;
}
}
private pumpState state;
public pumpState State
{
get
{
return state;
}
set
{
state = value;
}
}
public PumpItem(string _ID, pumpState _state, fuelSelection _selection)
{
this.pumpID = _ID;
this.FuelPumped = 0;
this.FuelCost = 0;
this.Selection = _selection;
this.State = _state;
}
}
}
As I said, I'm very new to WPF so would greatly appreciate any guidance or solutions. Thanks.
Ash was completely correct but here is a quick example for you to skim. This is and example of aViewModelBase which typically all ViewModels inherit from. From one of my repos. This how you would call your OnChangedEvent.
private sample _Item;
public sample Item
{
get
{
return _Item;
}
set
{
if (_Item != value)
{
_Item = value;
OnPropertyChanged("Item");
}
}
}
This will get it where you update everything when properties change.

Validating ItemsControl on Button click in WPF

I have an ItemsControl with an item template that contains two ComboBoxes. For any given item, the second ComboBox is required iff the first ComboBox has a selected value. I have set this validation up using IDataErrorInfo on the view model.
Rather than flagging ComboBox #2 as invalid the second a user selects a value in ComboBox1, I want to perform the validation when the user tries to save. It's kind of annoying to have a form "yell" at you for doing something wrong on a field you haven't even had a chance to enter yet.
Normally you could force this validation by retrieving the BindingExpression for the ComboBox and calling UpdateSource() and then determine if there is an error by calling Validation.GetHasError() passing the ComboBox. Since the ComboBoxes are generated dynamically by the ItemsControl, it is not as easy to get to. So I have 2 questions: 1. How do you ensure validation has executed for all controls when the save button is clicked. 2. How do you check whether there are validation errors when the save button is clicked. Validation.GetHasError remains false for the ItemsControl even when a ComboBox2 within it has an error. Thanks.
EDIT:
I had followed this article to implement IDataErrorInfo in order to validate the combobox properties relative to each other.
public class IntroViewModel : INotifyPropertyChanged, IDataErrorInfo
{
public Guid ClassScheduleID
{
get { return _intro.ClassScheduleID; }
set
{
_intro.ClassScheduleID = value;
OnPropertyChanged("ClassScheduleID");
//OnPropertyChanged("TrialDate"); //This will trigger validation on ComboBox2 when bound ComboBox1 changes
}
}
public DateTime TrialDate
{
get { return _intro.TrialDate; }
set
{
_intro.TrialDate = value;
OnPropertyChanged("TrialDate");
}
}
public string Error
{
get { return null; }
}
public string this[string columnName]
{
get { return ValidateProperty(columnName); }
}
private string ValidateProperty(string propertyName)
{
string error = null;
switch (propertyName)
{
case "TrialDate":
if (_intro.TrialDate == DateTime.MinValue && _intro.ClassScheduleID != Guid.Empty)
error = "Required";
break;
default:
error = null;
break;
}
return error;
}
}
I attempted to create the behavior you need based on some assumptions
sample
XAML
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Command="{Binding AddItem}"
Content="Add Item" />
<Button Command="{Binding Save}"
Content="Save" />
</StackPanel>
<ItemsControl ItemsSource="{Binding Data}"
Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border x:Name="border"
BorderThickness="1"
Padding="2"
Margin="2">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="value1" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox Text="{Binding Value1}"
ItemsSource="{Binding Source={StaticResource sampleData}}" />
<ComboBox Text="{Binding Value2}"
ItemsSource="{Binding Source={StaticResource sampleData}}"
Grid.Column="1" />
</Grid>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsValid}"
Value="False">
<Setter TargetName="border"
Property="BorderBrush"
Value="Red" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Main VM
public ViewModel()
{
AddItem = new SimpleCommand(i => Data.Add(new DataViewModel(new DataModel())));
Save = new SimpleCommand(i =>
{
foreach (var vm in Data)
{
vm.ValidateAndSave();
}
}
);
Data = new ObservableCollection<DataViewModel>();
}
public ObservableCollection<DataViewModel> Data { get; set; }
public ICommand AddItem { get; set; }
public ICommand Save { get; set; }
data VM and model
public class DataModel
{
public object Value1 { get; set; }
public object Value2 { get; set; }
}
public class DataViewModel : INotifyPropertyChanged
{
DataModel model;
public DataViewModel(DataModel model)
{
this.model = model;
IsValid = true;
}
object _value1;
public object Value1
{
get
{
return _value1;
}
set
{
_value1 = value;
}
}
object _value2;
public object Value2
{
get
{
return _value2;
}
set
{
_value2 = value;
}
}
public bool IsValid { get; set; }
public void ValidateAndSave()
{
IsValid = !(_value1 != null && _value2 == null);
PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));
if (IsValid)
{
model.Value1 = _value1;
model.Value2 = _value2;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
so the VM will validate all the items when you click save and will save only those items which are valid. otherwise will mark the IsValid property to false which will be notified to UI
I can't tell how you've implemented the IDataErrorInfo interface in your code, but in my implementation, doing what you want is simple. For future users, you can find out about this interface on the IDataErrorInfo Interface page on MSDN. On the linked page, you will see that you need to implement the Item indexer and the Error property.
That's all you need, because if you have implemented it correctly, then you can find out if your data (implementing) item has an error by simply checking the value of the Error property:
bool hasError = string.IsNullOrEmpty(yourDataTypeInstance.Error);
if (!hasError) Save(yourDataTypeInstance);
else MessageBox.Show("Invalid data!");
UPDATE >>>
Try using this instead:
public DateTime TrialDate
{
get { return _intro.TrialDate; }
set
{
_intro.TrialDate = value;
OnPropertyChanged("TrialDate");
OnPropertyChanged("Error");
}
}
public string Error
{
get { return this["TrialDate"]; }
}
I'll leave you to work out the rest, which is essentially managing strings.
Here is how I accomplished it while waiting for answers. When a save is intiated, ValidateTrials() is called to ensure validation has fired for the comboboxes and then TrialsHaveErrors() is called to check whether there are validation errors on them. This is the brute force approach I'd like to avoid, but it does work.
//Force validation on each combobox2
private void ValidateTrials()
{
foreach (IntroViewModel introVm in icTrials.Items)
{
ContentPresenter cp = (ContentPresenter)icTrials.ItemContainerGenerator.ContainerFromItem(introVm);
if (cp == null) continue;
ComboBox cb2 = (ComboBox)cp.ContentTemplate.FindName("cb2", (FrameworkElement)cp);
//Update the source to force validation.
cb2.GetBindingExpression(ComboBox.SelectedValueProperty).UpdateSource();
}
}
//Recursively searches the Visual Tree for ComboBox elements and checks their errors state
public bool TrialsHaveError(DependencyObject ipElement)
{
if (ipElement!= null)
{
for (int x = 0; x < VisualTreeHelper.GetChildrenCount(ipElement); x++)
{
DependencyObject child = VisualTreeHelper.GetChild(ipElement, x);
if (child != null && child is ComboBox)
{
if (Validation.GetHasError(child))
return true;
}
if (TrialsHaveError(child)) return true; //We found a combobox with an error
}
}
return false;
}
Slimmed down XAML:
<ItemsControl Name="icTrials" ItemsSource="{Binding Intros}" Margin="10,6,10,0" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Grid.Row="2">
<ComboBox Name="cb1"
SelectedValuePath="ID"
SelectedValue="{Binding Path=ClassScheduleID, Converter={StaticResource nullEmptyConverter}, ConverterParameter=System.Guid}"
ItemsSource="{Binding ClassesSource}">
<ComboBox.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<ComboBox Name="cb2"
ItemsSource="{Binding AvailableStartDates}"
DisplayMemberPath="Date"
ItemStringFormat="{}{0:d}"
SelectedValue="{Binding Path=TrialDate, Converter={StaticResource nullEmptyConverter}, ConverterParameter=System.DateTime, ValidatesOnDataErrors=True}">
</ComboBox>
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
To avoid the issue of flagging the field invalid before the user has had a chance to set it, I updated the setter for cb1's bound property, ClassScheduleID to conditionally fire notification for the TrialDate property depending on how the value is changing.

Design item template with xaml

I have to design a calculator like interface.
i'm utterly confused with how to do this.can any provide me with an insight?
how to bind data with these buttons at runtime.?
Ok got this working - basically I created the following XAML layout and with the following bindings:
<Grid x:Name="grdItems">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Content="{Binding Items[0].ItemName}"></Button>
<Button Content="{Binding Items[1].ItemName}" Grid.Column="1"></Button>
<Button Content="{Binding Items[2].ItemName}" Grid.Column="2"></Button>
<Button Content="{Binding Items[3].ItemName}" Grid.Row="1"></Button>
<Button Content="{Binding Items[4].ItemName}" Grid.Row="1" Grid.Column="1"></Button>
<Button Content="{Binding Items[5].ItemName}" Grid.Row="1" Grid.Column="2"></Button>
<Button Content="{Binding Items[6].ItemName}" Grid.Row="2"></Button>
<Button Content="{Binding Items[7].ItemName}" Grid.Row="2" Grid.Column="1"></Button>
<Button Content="{Binding Items[8].ItemName}" Grid.Row="2" Grid.Column="2"></Button>
<Button Content="Prev" Command="{Binding MovePrevCommand}" Grid.Row="3"></Button>
<Button Content="{Binding Items[9].ItemName}" Grid.Row="3" Grid.Column="1"></Button>
<Button Content="Next" Command="{Binding MoveNextCommand}" Grid.Row="3" Grid.Column="2"></Button>
</Grid>
This gives you the grid layout
Then I created a collection manager class that would do the 'windowed view'
class TestCollection
{
public ObservableCollection<TestItem> Items { get; set; }
List<TestItem> _items = new List<TestItem>();
int pos = 0;
public TestCollection(int size)
{
MoveNextCommand = new Command(new Action(MoveNext));
MovePrevCommand = new Command(new Action(MovePrev));
Items = new ObservableCollection<TestItem>();
for (int i = 0; i < size; i++)
{
_items.Add(new TestItem("Item " + i.ToString()));
}
UpdateItems();
}
public void MoveNext()
{
pos += 10;
if (pos > _items.Count - 10)
pos = _items.Count - 10;
UpdateItems();
}
public ICommand MoveNextCommand { get; set; }
public ICommand MovePrevCommand { get; set; }
public void MovePrev()
{
pos -= 10;
if (pos < 0)
pos = 0;
UpdateItems();
}
private void UpdateItems()
{
Items.Clear();
foreach (var i in _items.Skip(pos).Take(10))
{
Items.Add(i);
}
}
}
I created a simple implementation of ICommand to call a delegate:
class Command : ICommand
{
Action CallBack = null;
public Command(Action cb)
{
CallBack = cb;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
CallBack();
}
}
TestItem is just a simple class with an ItemName property
class TestItem
{
public string ItemName { get; set; }
public TestItem(string itemName)
{
ItemName = itemName;
}
}
Then in the main app code I added
var i = new TestCollection(2000);
grdItems.DataContext = i;
To wire the collection up to the grid. Works pretty well - you can add command bindings to your items to get the desired effect if you push the buttons (assuming you need buttons for each item of course!)
Let me know if that helps you get started or if there is anything you don't understand (or if this even works on Metro!!)
Edit: Just reading up and it seems IObservableVector now replaces ObservableCollection in WinRT
This was Jan 2012 so it may be that since then updates have added an implementation of ObservableVector - but from this article it seems you need to implement it
http://blogs.u2u.be/diederik/post/2012/01/03/Hello-ObservableVector-goodbye-ObservableCollection.aspx
Code is there anyway so no brain power required!

Categories