Design item template with xaml - c#

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!

Related

WPF How can a button in an ItemsControl operate independently from each other button in a custom expander?

I am trying to create my own expander control.
It has a header and a body. The header is a button that shows or hides the body.
This is all in an <ItemsControl/> that is bound to an observable collection - Each object in the observable collection creates its own expander control.
For the most part, this is working. What I am having trouble with is the expanding part. When clicking on the header, all the controls show or hide their contents at the same time - I know what the problem is: All the properties are bound to a value at the same time. How do I make them operate independently from one another?
I am not even sure that the way I am trying to handle this is the best way (I could use the expander control but I am still learning WPF and XAML and would like to recreate some standard controls for practice and understanding). I am still trying to figure out how bindings work; mostly RelativeSource and everything associated with it.
I would also like to pass a selected object as a parameter through the x:name="btn_body" button.
My current XAML:
<ItemsControl ItemsSource="{Binding testName}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel x:Name="parent" Margin="50,5" Background="Yellow"
Height="{Binding DataContext.ParentHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<DockPanel x:Name="child_header" Height="50" DockPanel.Dock="Top" Background="Aqua">
<Button Height="50" x:Name="btn_header"
Command="{Binding DataContext.OpenCloseBoxCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<StackPanel Orientation="Horizontal">
<TextBlock Text=" CONTENT "/>
</StackPanel>
</Button>
</DockPanel>
<DockPanel x:Name="child_body" DockPanel.Dock="Top"
Visibility="{Binding DataContext.Visable, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}"
Height="{Binding DataContext.ChildHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:MainWindow}}">
<Grid ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Button x:Name="btn_body" Grid.Column="2" Grid.Row="3" Grid.RowSpan="3" Margin="35,5" Content="CONFIRM"/>
</Grid>
</DockPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The ViewModel for the above (DataContext is set in the MainWindow code behind)
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
string content = File.ReadAllText(#"C:\\tempfiles\\people.json");
var listOfPeople = JsonConvert.DeserializeObject<apiResponse>(content);
testName = new ObservableCollection<person>(listOfPeople.people);
OpenCloseBoxCommand = new RelayCommand(OpenClose);
}
public RelayCommand OpenCloseBoxCommand { get; set; }
public ObservableCollection<person> testName { get; set; }
private int _parentheight = 150;
public int ParentHeight
{
get { return _parentheight; }
set { _parentheight = value; }
}
private Visibility _visable = Visibility.Visible;
public Visibility Visable
{
get { return _visable; }
set { _visable = value; }
}
private int _childheight = 100;
public int ChildHeight
{
get { return _childheight; }
set { _childheight = value; }
}
public void OpenClose()
{
if(_parentheight == 150)
{
_parentheight = 50;
_visable = Visibility.Hidden;
OnPropertyChanged("ParentHeight");
} else
{
_parentheight = 150;
_visable = Visibility.Visible;
OnPropertyChanged("ParentHeight");
}
}
}
EDIT
I have moved the properties over to the person class, along with the command. The general functionality works as expected now, but, Is the following the correct way to do things?
I understand the idea behind the MVVM pattern and as such I am trying to keep the UI stuff away from everything else as what I am trying to achieve is purely UI logic and non critical to functionality (this expander could be replaced by a grid and everything would function normally).
It just seems that UI "logic" is creeping into where it shouldn't be.
public class person : ViewModelBase { //needed for OnPropertyChanged
public person()
{
OpenCloseBoxCommand = new RelayCommand(OpenClose);
}
public RelayCommand OpenCloseBoxCommand { get; set; }
public string PersonId { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
private int _parentheight = 150;
public int ParentHeight
{
get { return _parentheight; }
set { _parentheight = value; }
}
private Visibility _visable = Visibility.Visible;
public Visibility Visable
{
get { return _visable; }
set { _visable = value; }
}
private int _childheight = 100;
public int ChildHeight
{
get { return _childheight; }
set { _childheight = value; }
}
public void OpenClose()
{
if (_parentheight == 150)
{
_parentheight = 50;
_visable = Visibility.Hidden;
OnPropertyChanged("ParentHeight");
}
else
{
_parentheight = 150;
_visable = Visibility.Visible;
OnPropertyChanged("ParentHeight");
}
}
}
ParentHeight and Visable should be property of person,not MainWindowViewModel.
In addition, as a member of MainWindowViewModel,OpenCloseBoxCommand should be added a parameter for pass current item.
I want to know why do not you choose Expander control?
I ever implemennt a Expander in Listbox,it worked well.

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));
}
}

UI not updating despite ObservableCollection (UWP, XAML) [duplicate]

This question already has answers here:
ObservableCollection not noticing when Item in it changes (even with INotifyPropertyChanged)
(21 answers)
Closed 6 years ago.
I would greatly appreciate some help with this binding issue I'm having. Basically I have a list view showing some information about Files. In the list view item itself, there's some text and also a button.
When this button is clicked I want to disable that button.
Currently I've set up an ObservableCollection - however even though the button click is being registered, the UI doesn't update. If I go to a different screen and return, then the UI updates. So it's not instantaneous.
I think there is some problem with the way RaisePropertyChanged() is working. I know from reading other SO articles that property changes in the object are harder to pick up than say, removing an item or adding an item to the ListView.
I'm completely stuck, any help would be most appreciated. Thanks.
Xaml:
<ListView RelativePanel.Below="heading" ItemsSource="{Binding Pages}" ReorderMode="Enabled" CanReorderItems="True" AllowDrop="True" Margin="0,10" SelectedItem="{Binding Path=SelectedFile,Mode=TwoWay}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="model:File">
<Grid Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{x:Bind Path= Name, Mode=TwoWay}" FontWeight="Bold" Padding="0,5" />
<TextBlock Text ="{x:Bind Path}" Grid.Row="1" TextWrapping="Wrap" Padding="10,0,0,0" Foreground="DarkGray" Opacity="0.8" />
<Button Content="X" Grid.Column="1" Grid.RowSpan="2" Command="{x:Bind EnableCommand}" IsEnabled="{x:Bind Path=IsEnabled, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
File.cs:
public class File : ViewModelBase
{
public string Name { get; set; }
public string FileName { get; set; }
public string Path { get; set; }
public string Contents { get; set; }
private Boolean isEnabled = true;
public Boolean IsEnabled {
get { return isEnabled; }
private set {
isEnabled = value;
RaisePropertyChanged("IsChecked");
}
}
private ICommand enableCommand;
public ICommand EnableCommand
{
get
{
if(enableCommand == null)
{
enableCommand = new RelayCommand(() => {
isEnabled = false;
Name += "Disabled";
RaisePropertyChanged();
});
}
return enableCommand;
}
}
}
Viewmodel:
public class MyPageViewModel : BaseViewModel
{
private ObservableCollection<File> pages;
public ObservableCollection<File> Pages
{
get { return pages; }
set
{
pages = value;
RaisePropertyChanged();
}
}
private File selectedFile = new File();
public File SelectedFile
{
get { return selectedFile; }
set
{
Set(ref selectedFile, value);
}
}
public MyPageViewModel()
{
if (ApplicationData.FileList != null)
{
Pages = new ObservableCollection<File>(ApplicationData.FileList);
}
else
{
Pages = new ObservableCollection<File>();
}
}
You notify IsChecked when you should be notifying IsEnabled.
(ObsevarvableCollection only notifies when something is added or removed from it. Changes in the objects it holds are not notified by it.)

Bound observable collection update without 'RemoveOf' & 'Insert'

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++;
}
}

Categories