How to set tab order? - wpf

I've three Classes in my project Master, Person and Command. Master has two properties, a constructor and overridden the ToString:
class Master {
public string FirstName { get; set; }
public string LastName { get; set; }
public Master(string FirstName, string LastName) {
this.FirstName = FirstName;
this.LastName = LastName;
}
public override string ToString() {
return FirstName + " " + LastName;
}
}
Command is an implementation of ICommand
class Command : ICommand {
Func<object, bool> CanDo { get; set; }
Action<object> Do { get; set; }
public event EventHandler CanExecuteChanged;
public Command(Func<object, bool> CanDo, Action<object> Do) {
this.CanDo = CanDo;
this.Do = Do;
CommandManager.RequerySuggested += (o, e) => Evaluate();
}
public bool CanExecute(object parameter) => CanDo(parameter);
public void Execute(object parameter) => Do(parameter);
public void Evaluate() => CanExecuteChanged?.Invoke(null, EventArgs.Empty);
}
and Person has two properties, implemented INotifyPropertyChanged, is an ObservableCollection<Master> and using Command:
class Person : ObservableCollection<Master>, INotifyPropertyChanged {
string firstName, lastName;
public string FirstName {
get => firstName;
set { firstName = value; OnPropertyChanged(); }
}
public string LastName {
get => lastName;
set { lastName = value; OnPropertyChanged(); }
}
public Command AddToList { get; set; }
public new event PropertyChangedEventHandler PropertyChanged;
public Person() {
AddToList = new Command(CanDo, Do);
}
void OnPropertyChanged([CallerMemberName] string name = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
bool CanDo(object para) => !string.IsNullOrEmpty(firstName) && !string.IsNullOrEmpty(lastName);
void Do(object para) {
Add(new Master(firstName, firstName));
FirstName = LastName = null;
}
}
On xaml I've these:
<Window ...>
<Window.Resources>
<local:Person x:Key="Person"/>
</Window.Resources>
<Grid DataContext="{StaticResource Person}">
<StackPanel>
<TextBox Text="{Binding FirstName}"/>
<TextBox Text="{Binding LastName}"/>
<Button Content="Click" Command="{Binding AddToList}"/>
<ListView ItemsSource="{Binding}"/>
</StackPanel>
</Grid>
</Window>
I've to click on the first TextBox, bound to FirstName, after launching the app to type something there, by pressing Tab I can type in the second TextBox and if I then hit Tab again, it instead of focusing the Button goes back to first TextBox so I've to hit Tab twice to get to the Button and by hitting Enter or Space I can add the item in the ListView.
At this point I'm not sure what's focused, I've to hit Tab once more to get to the first TextBox. After typing some more text in first as well as second TextBoxes if I hit Tab, it instead of focusing Button or first TextBox selects the ListView so I've to hit Tab thrice to get to the Button!
I want to give first TextBox focus when the app launches and after hitting Tab on second TextBox I want it to go to the Button and exclude ListView from focus. I've tried setting Focusable="False", KeyboardNavigation.IsTabStop="False", IsTabStop="False" in ListView but those don't work! I also have tried settingTabIndex on TextBoxes and Button like this:
<TextBox Text="{Binding FirstName}" TabIndex="1"/>
<TextBox Text="{Binding LastName}" TabIndex="2"/>
<Button Content="Click" Command="{Binding AddToList}" TabIndex="3"/>
These don't work either!

The problem you're having is that at the point you tab away from the second text box it needs to decide where to place the focus. However, at that point in time, the command is still disabled because it cannot be executed because the value from the text box has not arrived in the view model yet. This value arrives after the focus has been moved.
One way to fix this would be to change the binding of the second textbox so that the ViewModel is updated on every change to the value. Add the following clause to that binding:
UpdateSourceTrigger=PropertyChanged
More details here... https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/how-to-control-when-the-textbox-text-updates-the-source
Now whenever you type a character the command will reconsider whether it can be executed.

The short answer:
Set UpdateSourceTrigger=PropertyChanged on your binding:
<TextBox Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}"/>
I suspect what is happening is that WPF is evaluating the next tab position before the command CanExecute is evaluated, so when it is working out where to tab to next the button is still disabled, hence the only place it has to go is back to the first text box.
UpdateSourceTrigger tells WPF to evaluate the bindings on each keypress instead of when the focus changes, which means the button is correctly enabled by the time it needs to work the tab stops out.

Related

Updating values in ObservableCollection

Hey I have an ObservableCollection which consists of a class with two attributes (strings = User and Response) bound to a listbox.
I would like to have the users in the listbox first, which I add with this:
for (int i = 0; i < ArrStrUser.Length; i++)
{
Users.Add(new User() { input = ArrStrUser[i].Trim() });
}
I want to add the responses to the respective user later.
If I do this, they will be added to the ObservableCollection but not update in the listbox.
Users[i].response = strOutput.Trim().Replace(Environment.NewLine, " ");
The ObservableCollecton
private ObservableCollection<Input> Users = new ObservableCollection<Input>();
The Class:
public class Input
{
public string user{ get; set; }
public string response { get; set; }
}
XAML:
<ListBox x:Name="LBresponse" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource UserTemplate}" />
<DataTemplate x:Key="UserTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path= user}" Width="50"/>
<TextBlock Text="{Binding Path= response}" />
<Button Content="Delete" Click="DeleteUser_Clicked" HorizontalAlignment="Left"/>
</StackPanel>
</DataTemplate>
Simple solution
Your Input class needs to implement the INotifyPropertyChanged interface and invoke the PropertyChanged event upon changing property's value in order to update the ListBox. The ObservableCollection only "cares" about adding or removing items, it doesn't handle item's property changing.
Try editing your input class like this:
public class Input : INotifyPropertyChanged
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set {
_response = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Now changing the Response property should update the UI.
Better solution
I'd also advise you to separate the INotifyPropertyChanged implementation into its own class if you want to use it somewhere else, too. Or better yet, use a library that already has it, like the mvvm-helpers nuget package by James Montemagno.
Here's a link to the INotifyPropertyChanged implementation from that library
This is how you use it:
public class Input : ObservableObject
{
public string user{ get; set; }
private string _response;
public string Response{
get => _response;
set => SetProperty(ref _response, value);
}
}
It also supports passing in an OnChanged Action and a validation function.

How to capture a button press in a ListView

I'm displaying a list of items in a WPF ListView, the items have a Quantity, Order Code and a Description. The columns are bound to fields in an ObservableCollection held in the View Model. This is all very standard and works as would expect. However, in the Quantity Column of the ListView I am adding two button + and -, the idea being that when they are pressed the value of the quantity either increments or decrements. The problem is that because these buttons are not bound to a field in the ObservableCollection I cannot get a link from the button being pressed in the List View to the record in the ObservableCollection. I have tried getting the item selected in the ListView but it is the button that gets selected when pressed and not the ListView item, I have also captured the item beneath the mouse pointer when the button is pressed but it could be pressed using the keyboard.
I feel there must be a (simple!) way of doing this but I can't find it.
This is the XAML:
<ListViewName="AccessoriesContent" >
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Select">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="QuantityStack">
<Button Name="SubtractAccessoryButton" Command="vx:DataCommands.SubtractAccessory" Content="-" />
<TextBox Name="QuantityTextBox" Text="{Binding Quantity, Mode=TwoWay}" />
<Button Name="AddAccessoryButton" Command="vx:DataCommands.AddAccessory" Content="+" />
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Order Code" DisplayMemberBinding="{Binding OrderCode}" />
<GridViewColumn Header="Description" DisplayMemberBinding="{Binding Description}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
The code behind:
public MainWindow()
{
//CommandBindings.Add(
InitializeComponent();
AccessoryVM = new AccessoryViewModel();
AccessoriesContent.ItemsSource = AccessoryVM.AccessoryCollection;
}
And the ViewModel:
class AccessoryViewModel
{
ObservableCollection<AccessoryData> _AccessoryCollection =
new ObservableCollection<AccessoryData>();
public ObservableCollection<AccessoryData> AccessoryCollection
{ get { return _AccessoryCollection; } }
public void PopulateAccessories(string order_code)
{
// Read the data and populate AccessoryCollection
}
}
public class AccessoryData : INotifyPropertyChanged
{
private int _quantity;
public int Quantity
{
get { return _quantity; }
set
{
this._quantity = value;
Notify("Quantity");
}
}
public string OrderCode { get; set; }
public string Description { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
Beyond this I have two methods SubtractAccessory and AddAccessory which are triggered by the buttons but I have yet to populate them with anything that would work.
Another option is to create a RelayCommand (see here). In this model you create an ICommand property on each of your items. You then set this property to a new RelayCommand that accepts a delegate you would like to be ran when that command is activated. So this could be a QuantityUp method and a QuantityDown method on your AccessoryData. Once you've got your ICommand property in place you simply bind to it like this, where QuantityUpCommand is your ICommand property.
<GridViewColumn Header="" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Height="15" Width="15" Content="+" Command="{Binding QuantityUpCommand}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
The AccessoryData would look something like this
private RelayCommand _quantityUpCommand;
public ICommand QuantityUpCommand
{
get
{
if (_quantityUpCommand == null)
{
_quantityUpCommand = new RelayCommand(QuantityUp);
}
return _quantityUpCommand;
}
}
public void QuantityUp(object obj)
{
Quantity++;
}
And RelayCommand looks something like this:
public class RelayCommand: ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
You do not appear to have posted the commands you use.
Anyway, if you do use commands you could either use instance commands which exist on the view model (you then will need to bind the command to the command property on the DataContext) and hence have access to the Quantity or you can pass the view model as CommandParameter just setting it to {Binding}, then in the command you can cast the parameter to the VM and change the Quantity.
(If you were to use the Click event you could just cast the sender to Button and cast its DataContext to the VM)
You could pass the current item through a CommandParameter on the button that uniquely identifies the current item. So that in the execution of the Command you know what item you're talking about. If you can't find a unique token in your item, you could even pass the whole item!
<Button Name="AddAccessoryButton" Command="vx:DataCommands.AddAccessory" CommandParameter="{Binding}" Content="+" />

Child item in TreeView not updating

I copied the basic method of having checkbox in a treeview from the official Silverlight toolkit Checkboxes in a TreeView example.
When a user clicks on a parent TreeViewItem I want all of the child items to be checked, as in the above example. This works fine when the parent is collapsed, clicking the checkbox puts a tick in the parent and when you expand the node all children have a tick in the checkbox.
However it doesn't work if the parent is expanded. None of the children are updated to have a tick in the checkbox, although the underlying data list is updated.
My XAML is as follows:
<sdk:HierarchicalDataTemplate x:Key="NodeTemplate" ItemsSource="{Binding Path=Contracts}">
<StackPanel Orientation="Horizontal" ToolTipService.ToolTip="{Binding Path=Name}">
<CheckBox IsTabStop="False" IsThreeState="{Binding Path=HasContracts}" IsChecked="{Binding Path=Selected, Mode=TwoWay}" Click="CheckBox_Click" />
<TextBlock Text="{Binding Path=Name}" Tag="{Binding Path=ID}"/>
</StackPanel>
</sdk:HierarchicalDataTemplate>
<sdk:TreeView x:Name="tvClientContract" ItemsSource="{Binding Path=ClientContracts, Mode=TwoWay}" ItemTemplate="{StaticResource NodeTemplate}"/>
This is bound to a List<ClientContract> and uses the same code behind as in the linked example.
The ClientContract object is:
public int ID { get; set; }
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
How can I force the child to repaint itself as the underlying List<ClientContract> object is updated?
If you want to use INotifyPropertyChange(what I did instead of using ObservableCollection) here is how you do it per example on the ID element:
public class myclass : INotifyPropertyChanged
{
private int id_Value;
public int ID
{
get { return id_Value; }
set
{
id_Value = value;
NotifyPropertyChanged("ID");
}
}
public string Name { get; set; }
public List<ClientContract> Contracts { get; set; }
public bool? Selected { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
I hope this helps if it was what you were trying to do.
Try using ObservableCollection<ClientContract> instead of a List<>. Usually you want to databind to this collection type instead when the data is dynamic so it can notify the UI of collection changes.

Silverlight datagrid, show details for more than one row, but not all of them

I have a datagrid in which I would like to show the details when a row is clicked, and hide it when the user clicks again.
DataGridRowDetailsVisibilityMode.VisibleWhenSelected
only allows to show one details row at a time. The other options seem to be All or Nothing.
What is the workaround?
Thanks!
You could manage the state yourself.
Have the visibility of the detail bound to property on the underlying object and simply toggle the value of this property when the row is selected or deselected.
May be it is possible to set DataGridRowDetailsVisibilityMode to Visible, and change row details template to be hidden or expanded depends of condition your need.
OK. Based on the request for code...
Here is the XAML for a hypothetical grid:
<data:DataGrid x:Name="TheGrid" AutoGenerateColumns="False">
<data:DataGrid.Columns>
<data:DataGridTemplateColumn Header="Items">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid MouseLeftButtonDown="Item_MouseLeftButtonDown">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Title}" Margin="5"/>
<TextBlock Grid.Column="1" Text="{Binding Desc}" Visibility="{Binding DescVisibility}" Margin="5"/>
</Grid>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
Note the use of a DataTemplate for the column and the inclusion of two text items. The second TextBlock has its visibility bound to DescVisibility. Also note the click event on the grid.
Here is the code for the data object that we are binding to each row:
public class AnItem : INotifyPropertyChanged
{
public AnItem(string title, string desc)
{
Title = title;
Desc = desc;
}
public string Title { get; set; }
public string Desc { get; set; }
private bool _toggleState { get; set; }
public bool ItemToggled
{
get { return _toggleState; }
set
{
if (_toggleState != value)
{
_toggleState = value;
OnPropertyChanged("ItemToggled");
OnPropertyChanged("DescVisibility");
}
}
}
public Visibility DescVisibility
{
get
{
if (_toggleState)
return Visibility.Visible;
else
return Visibility.Collapsed;
}
}
#region INotifyPropertyChanged implementation
/// <summary>
/// This event is fired when any of the property values change on this object
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises the PropertyChanged event for the passed in property
/// </summary>
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged Implementation
}
This object implements INotifyPropertyChanged so that it plays nice with binding and it has two special properties. A read/write boolean for maintaining state and a read only visibility value which simply saves us from using a boolean/visibility converter in the binding because I'm lazy.
The final piece is the event handler for the click event which is pretty simple:
private void Item_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (((Grid)sender).DataContext is AnItem)
{
AnItem item = ((Grid)sender).DataContext as AnItem;
item.ItemToggled = !item.ItemToggled;
}
}
Finally, for completeness only, here is the code to set the item source for the grid:
public List<AnItem> TheItems = new List<AnItem>();
public MainPage()
{
InitializeComponent();
TheItems.Add(new AnItem("Title1", "The description for the first item"));
TheItems.Add(new AnItem("Title2", "The description for the second item"));
TheItems.Add(new AnItem("Title3", "Maybe I should be more imaginative with descriptions"));
TheGrid.ItemsSource = TheItems;
}
Hope this helps.

WPF Data binding issue using mvvm pattern

I have created a user control "SearchControl"(which will be reused further in other screens as well.
SearchControl ->
<usercontrol name="SearchControl"......>
<stackpanel orientation="horizontal"...>
<TextBox Text"{Binding Path=UserId}"...>
<Button Content="_Search" ....Command="{Binding Path=SearchCommand}"..>
</stackpanel>
</usercontrol>
public partial class SearchControl : UserControl
{
public SearchControl()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
I then use this control in a window "UserSearch"
<window name="UserSearch".............
xmlns:Views="Namespace.....Views">
<Grid>
<Grid.RowDefinitions>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition..../>
<ColumnDefinition..../>
</Grid.ColumnDefinitions>
<Views:SearchControl Grid.Row="0" Grid.Colspan="2"/>
<TextBlock Text="User Id" Grid.Row="1" Grid.Column="0"..../>
<TextBox Text="{Binding Path=UserId}" Grid.Row="1" Grid.Column="1".../>
<TextBlock Text="First Name" Grid.Row="2" Grid.Column="0"..../>
<TextBox Text="{Binding Path=FirstName}" Grid.Row="2" Grid.Column="1".../>
<TextBlock Text="Last Name" Grid.Row="3" Grid.Column="0"..../>
<TextBox Text="{Binding Path=LastName}" Grid.Row="3" Grid.Column="1".../>
</Grid>
</window>
public partial class UserSearch : Window
{
public UserSearch()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
What I am aimimg for:
When I enter UserId inthe textbox in SearchControl and click on Search button, the resulting record which is retieved should be displayed in the textboxes for UserId, FirstName, LastName
class UserViewModel:INotifyPropertyChanged
{
DBEntities _ent; //ADO.Net Entity set
RelayCommand _searchCommand;
public UserViewModel()
{
_ent = new DBEntities();
}
public string UserId {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public ICommand SearchCommand
{
get
{
if(_searchCommand == null)
{
_searchCommand = new RelayCommand(param = > this.Search());
}
return _searchCommand;
}
}
public void Search()
{
User usr = (from u in _ent
where u.UserId = UserId
select u).FirstOrDefault<User>();
UserId = usr.UserId;
FirstName = usr.FirstName;
LastName = usr.LastName;
OnPropertyChanged("UserId");
OnPropertyChanged("FirstName");
OnPropertyChanged("LastName");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertChangedEventArgs(propertyName);
}
}
Here as I am using two separate instances of the UserViewModel for the SearchControl and UserSearch, even though I retieve the record for the particular user on searching by UserId, I am unable to bind the properties UserId, FullName , LastName with the respective textboxes...How do I fix this problem??
1) Don't let the View initialize the presentation model, it should be the other way round. The presentation model is the object of interest, not the particular view.
public interface IView
{
void SetModel(IPresentationModel model);
}
publiv class View : UserControl, IView
{
public void SetModel(IPresentationModel model)
{
DataContext = model;
}
}
public class PresentationModel : IPresentationModel
{
public PresentationModel(IView view)
{
view.SetModel(this);
}
}
2) Don't set the data context of the subview in the code behind file. Usually, the view that uses the subview sets the data context in the xaml file.
3) Usually each view has its own presentation model. The presentation model should have one type of view. That means that different views of a single presentation model may differ in appearance but not in functionality (in your case one view is used to search, the other one is used to display and edit data). So, you have vialoted the Single Responsibilty Principle.
4) Abstract your data access layer, otherwise you won't be able to unit test your presentation model (because it needs access to the data base directly). Define an repository interface and implementation:
public interface IUserRepository
{
User GetById(int id);
}
public class EntityFrameworkUserRepository : IUserRepository
{
private readonly DBEntities _entities;
public EntityFrameworkUserRepository(DBEntities entities)
{
_entities = entities;
}
public User GetById(int id)
{
return _entities.SingleOrDefault(u => u.UserId == id);
}
}
5) Don't use FirstOrDefault because an ID is unique, so there must not be several users for one id. SingleOrDefault (used in the code snippet above) throws an exception if more than one result is found but returns null if none is found.
6) Bind directly to your entity:
public interface IPresentationModel
{
User User { get; }
}
<StackPanel DataContext="{Binding Path=User}">
<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
</StackPanel>
7) Use the CommandParameter to provide the user id you are searching for directly with your command.
<TextBox x:Name="UserIdTextBox">
<Button Content="Search" Command="{Binding Path=SearchCommand}"
CommandParameter="{Binding ElementName=UserIdTextBox, Path=Text}" />
public class PresentationModel
{
public ICommand SearchCommand
{
// DelegateCommand<> is implemented in some of Microsoft.BestPractices
// assemblies, but you can easily implement it yourself.
get { return new DelegateCommand<int>(Search); }
}
private void Search(int userId)
{
_userRepository.GetById(userId);
}
}
8) If only data binding causes issues, look at the following website to get some ideas how to debug wpf data bindings: http://beacosta.com/blog/?p=52
9) Don't use strings that contain property names. Once you refactor your code and properties change their names, to will have a stressful time finding all property names in strings and fixing them. Use lambda expressions instead:
public class PresentationModel : INotifiyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (value == _value) return;
_value = value;
RaisePropertyChanged(x => x.Value);
}
}
public PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(Expression<Func<PresentationModel, object>> expression)
{
if (PropertyChanged == null) return;
var memberName = ((MemberExpression)expression.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
I wish you the best to solve your problem and I hope that I could help you a little bit.
Best Regards
Oliver Hanappi

Resources