Keybinding a TextBox using XAML and WPF in C# - c#

I'm trying to bind to a key event in a WPF UserControl. The component is a TextBox and my XAML is
<TextBox Name="textBarCode" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0,0,10,0" Width="300">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ImportPanel.BarcodeTextEnterKeyCommand}"/>
<KeyBinding Key="Tab" Command="{Binding ImportPanel.BarcodeTextTabKeyCommand}"/>
</TextBox.InputBindings>
</TextBox>
I'm not sure if it is needed or not but the namespace declaration is
<UserControl x:Class="Scimatic.Samples.Actions.ImportPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ig="http://schemas.infragistics.com/xaml"
xmlns:components="clr-namespace:Scimatic.Mondrian.Components;assembly=Mondrian"
xmlns:sampleInventory="clr-namespace:Scimatic.Mondrian.Views.SampleInventory;assembly=Mondrian"
xmlns:trackingTags="clr-namespace:Scimatic.Mondrian.Views.TrackingTags;assembly=Mondrian">
The code that declares the command in the underlying xaml.cs file is
_barcodeKeyCommand = new ActionCommand(() =>
{
if (!_parent && FocusableTagOnBarcode != null)
{
trackingInfo.SetFocusOnTag(FocusableTagOnBarcode);
}
else
{
buttonImport.Focus();
}
});
The code that sets these properties is:
/// <summary>
/// The command property for the enter key in the barcode text box
/// </summary>
public ICommand BarcodeTextTabKeyCommand
{
get
{
return _barcodeKeyCommand;
}
}
The commands are returned in the same class using the same method:
/// <summary>
/// The command property for the enter key in the barcode text box
/// </summary>
public ICommand BarcodeTextEnterKeyCommand
{
get
{
return _barcodeKeyCommand;
}
}
However no matter what I try (and I've tried all kinds of things); I just cannot get the command to be called. I've clearly done something wring but could someone please help me. I'm fairly new to C# and I've wasted two days trying to respond to an enter key in a text box!
Thank you in advance,
Regards,
Neil

Binding will look for a specified binding Path in current binding context. By default it will be current DataContext. You can change binding context by using either ElementName, Source or RelativeSource. So in your case, assuming that BarcodeTextEnterKeyCommand is a property of ImportPanel control, you can give your control some name and then change command binding
<UserControl
x:Class="Scimatic.Samples.Actions.ImportPanel"
...
x:Name="myUserControl">
<!-- ... -->
<TextBox Name="textBarCode" ....>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding ElementName=myUserControl, Path=BarcodeTextEnterKeyCommand}"/>
<KeyBinding Key="Tab" Command="{Binding ElementName=myUserControl, Path=BarcodeTextEnterKeyCommand}"/>
</TextBox.InputBindings>
</TextBox>
<!-- ... -->
</UserControl>

Related

How can I bind a CommandParameter with a child UserControl's child element?

I have the following xaml view:
<UserControl x:Class="MyViews.PersonView"
xmlns:views="clr-namespace:MyViews"
[...]
>
[...]
<dxb:BarManager x:Name="MainBarManager">
<dxb:BarManager.Items>
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
/>
</dxb:BarManager.Items>
<Grid>
<Grid.RowDefinitions>
[...]
</Grid.RowDefinitions>
<views:CardView x:Name="CardUserControl" Grid.Row="2"/>
</Grid>
[...]
</UserControl>
The CardView is defined as follows:
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
</UserControl>
The PrintPersonsCommand is defined as follows in my ViewModel:
public class PersonViewModel
{
public PersonViewModel(...)
{
[...]
PrintPersonsCommand = new Prism.Commands.DelegateCommand<DataViewBase>(PrintPersons, CanPrintPersons);
}
public Prism.Commands.DelegateCommand<DataViewBase> PrintPersonsCommand { get; private set; }
private void PrintPersons(DataViewBase view)
{
_printService.ShowGridViewPrintPreview(view);
}
private bool CanPrintPersons(DataViewBase view)
{
return true;
}
}
Now, when I click the Print button, the above PrintPersons method is always fed with null. How do I pass CardUserControl.PersonsCardView in my MyViews.PersonView xaml above, how do I pass that PersonCardView to my command? In other words, how do I fix
CommandParameter="{Binding PersonsCardView, ElementName=CardUserControl}"
to make it work?
Currently, the only solution I've found to this problem is to replace the Command and CommandParameter with
ItemClick="OnPrintBtnClick"
and then in the PersonView's code-behind file to do:
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
var ctxt = DataContext as PersonViewModel;
ctxt.PrintPersonsCommand.Execute(CardUserControl.PersonsCardView);
}
That works but I can't believe there is no other way. I'm not happy with that solution because I don't have the benefits of using the Command any more, like e.g. the automatic evaluation of the Command's CanExecute method. I could also put the CardView's xaml code in the PersonView.xaml but I like my controls to be in separate files because I have the feeling it's more structured and each user control has its own responsibilities which can nicely be split into separate files. Also, that solution binds my view to my view model too tightly.
Can someone help me out please?
Without changing your existing view and viewmodel hierarchy, I was able to pass the GridControl.View to the PersonViewModel using the Tag property
You can assign the CardView to the Tag property at the bottom of your CardView UserControl, and then access this Tag as CommandParameter.
CardView UserControl
<UserControl x:Class="MyViews.CardView"
[...]>
[...]
<dxg:GridControl ItemsSource="{Binding Persons}" SelectedItems="{Binding SelectedPersons}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" SelectionMode="MultipleRow">
[...]
<dxg:GridControl.View>
<dxg:CardView x:Name="PersonsCardView"
[...]
CardTemplate="{StaticResource DisplayCardTemplate}"
PrintCardViewItemTemplate="{StaticResource PrintCardTemplate}"/>
</dxg:GridControl.View>
[...]
</dxg:GridControl>
<UserControl.Tag>
<Binding ElementName="PersonsCardView"/>
</UserControl.Tag>
</UserControl>
Print Button Xaml:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
Command="{Binding PrintPersonsCommand}"
CommandParameter="{Binding ElementName=CardUserControl, Path=Tag}"
/>
Based on the valuable input of Insane, I came up with the following two cleaner fixes:
Code-behind solution
In the PersonView, use the ItemClick event handler on the Print button:
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick"/>
Adapt the corresponding code-behind file like this:
public partial class PersonView : UserControl
{
readonly IPrintService _printService;
public PersonView(IPrintService printService)
{
_printService = printService;
InitializeComponent();
}
private void OnPrintBtnClick(object sender, ItemClickEventArgs e)
{
_printService.ShowGridViewPrintPreview(CardUserControl.PersonsCardView);
}
}
Because I want to gray-out the Print button when there is no selection, I still need to add some code to make that happen. I can get it by
1. updating the button code to
<dxb:BarButtonItem x:Name="bbiPrint"
Content="{Binding Print, Source={StaticResource CommonResources}}"
ItemClick="OnPrintBtnClick" IsEnabled="{Binding CanPrintPersons}"/>
refreshing the CanPrintPersons property in the PersonViewModel upon Persons selection change
That's it.
CardViewModel solution
In that solution, we have a PersonView with its underlying PersonViewModel and a CardView with its underlying CardViewModel. I will not describe that solution with all the details as it is overkill in my situation but for the sake of completeness, I'll give the main points. Upon clicking the Print button on the PersonView, the PersonViewModel's PrintCommand is called. That command emits a Print event to the CardViewModel which in turn calls its own PrintCommand. That latter command calls
_printService.ShowGridViewPrintPreview(View);
where the View is a CardViewModel's property that is set upon CardView loading with e.g.
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="Loaded" Command="{Binding ViewLoadedCommand}" CommandParameter="{Binding ElementName=PersonsCardView}" />
</dxmvvm:Interaction.Behaviors>
Because I have two child views I want to print, I'd need to add a view model for each one of those. In addition, those two view models plus the PersonViewModel need access to the list of Persons to be printed. In particular, they need a shared access to the same data, so that they are synchronized. A simple way to do that is explained here and is totally doable. But I think it is not worth the trouble for the simple use case I have as it adds more complexity than necessary.

MVVM Light RelayCommand binding not working in user control

I'm working on a WPF application which uses the MVVM Light toolkit. I'm creating a wizard and I want to show buttons for navigating to the previous step and the next step on every page. In order to avoid code duplication, I use a user control which provides the buttons.
I'm trying to bind the next button's Command property to a dependency property , which is defined in the code behind file, called NextStepCommand. The type of this property is RelayCommand. The user control's dependency property NextStepCommand should then be bound to the window's property with the exact same name NextStepCommand; of course it also is of type RelayCommand.
However, the binding from the window's property NextStepCommand to the button's property Command doesn't work. Binding the window's property NextStepCommand to an arbitrary button defined in the window's XAML file works fine; so does implementing a RelayCommand in the user control's code behind class and binding it to the next button defined in the user control. Nevertheless, the full link from the window's property to the user control's button does not work and I can't figure out a solution.
The user control's XAML code is shown below.
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DockPanel>
<Button DockPanel.Dock="Left"
IsEnabled="{Binding Path=PreviousStepEnabled}"
Command="{Binding Path=PreviousStepCommand}">Back</Button>
<Button DockPanel.Dock="Right"
IsEnabled="{Binding Path=NextStepEnabled}"
Command="{Binding Path=NextStepCommand, Mode=OneWay}">Next</Button>
<Label/>
</DockPanel>
</UserControl>
The user control's NextStepCommand is defined the following way:
public RelayCommand NextStepCommand
{
get { return (RelayCommand)GetValue(NextStepCommandProperty); }
set { SetValue(NextStepCommandProperty, value); }
}
public static readonly DependencyProperty NextStepCommandProperty =
DependencyProperty.Register(nameof(NextStepCommand), typeof(RelayCommand), typeof(WizardStepSwitchBar), new PropertyMetadata(default(RelayCommand)));
The window's XAML is displayed below.
<MahApps:MetroWindow
xmlns:MahApps="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
DataContext="{Binding Source={StaticResource Locator}, Path=BasicSettings}">
<Grid Style="{StaticResource MainContainerMargin}">
<control:WizardStepSwitchBar Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="3" PreviousStepEnabled="False" NextStepCommand="{Binding Path=NextStepCommand, Mode=OneWay}"/>
</Grid>
</MahApps:MetroWindow>
The implementation of the window's NextStepCommand property is quite simple:
public RelayCommand NextStepCommand
{
get
{
return new RelayCommand(this.OnNextStep);
}
}
private void OnNextStep()
{
MessageBox.Show("It works!");
}
I tried using this answer, but didn't provide a solution to my issue. Thank you in advance for your support!

CheckBox [Inside ListBox ItemTemplate] Checked Event is not triggering

This is my xaml
<ListBox x:Name="HistoryList"
ItemsSource="{Binding HistoryCollection}"
>
<ListBox.ItemTemplate >
</DataTemplate>
<CheckBox x:Name="UpCheckBox" Height="50" Width="50" >
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="Checked">
<interactivity:InvokeCommandAction Command="{Binding UpCheckedCommad}" CommandParameter="{Binding ElementName=UpCheckBox}"></interactivity:InvokeCommandAction>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</ListBox.ItemTemplate >
</ListBox >
In ViewModel I have used GalasoftMVVM Command Binding
public ICommand UpCheckedCommad
{
get { return new RelayCommand<Object>(x => { PerformUpforTracks(x); }); }
}
void PerformUpforTracks(object x)
{
//TODO
}
I used a CheckBox inside a ListBox ItemTemplate.But am not getting the Checked Event of CheckBox in the ViewModel .
I wanted to get the Checked Event from my ViewModel.Can anyone have any idea to resolve this issue?
Each instance of your ListBox.ItemTemplate is automatically given "the current item in the collection" as its DataContext. In your case, that is each individual item in the HistoryCollection. In your example, the EventTrigger is searching for the "ThumbsUpCheckedCommad" inside your current instance of the HistoryItem.
In order to force the EventTrigger to search in your desired ViewModel, you need to specify the "Source" property of your command binding. I suggest using the RelativeSource syntax, to search up the tree for the last Control to have your ViewModel as its DataContext.
It would look something like this.
{Binding Path=ThumbsUpCheckedCommand, RelativeSource={RelativeSource AncestorType={x:Type ListBox}}}
I got it By Binding Command by this way
Command="{Binding Path=DataContext.UpCheckedCommad,
ElementName=HistoryList}"

Listbox bound to ObservableCollection empty

I have the following xaml:
<Window x:Class="Retail_Utilities.Dialogs.AdjustPriceDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
ShowInTaskbar="False"
WindowStartupLocation="CenterOwner" Name="Adjust_Price"
Title="Adjust Price" Background="#ee0e1c64" AllowsTransparency="True" WindowStyle="None" Height="330" Width="570" KeyDown="Window_KeyDown" Loaded="Window_Loaded">
<Grid Height="300" Width="550">
<ListBox HorizontalAlignment="Right" Margin="0,110,35,60" Name="lstReasons" Width="120" VerticalAlignment="Stretch"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window, AncestorLevel=1}, Path=reasons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=POS_Price_Change_Reason}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Here is the relevant c#:
namespace Retail_Utilities.Dialogs
{
public partial class AdjustPriceDialog : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons; ...
and finally, here is the code from another page that opens this window:
AdjustPriceDialog apd = new AdjustPriceDialog();
apd.Owner = (Window)this.Parent;
apd.reasons = new ObservableCollection<Twr_POS_Price_Change_Reason>();
var pcr = from pc in ctx.Twr_POS_Price_Change_Reasons where pc.Deactivated_On == null select pc;
foreach (Twr_POS_Price_Change_Reason pc in pcr)
{
apd.reasons.Add(pc);
}
apd.AdjustingDetail = (Twr_POS_Invoice_Detail)lstDetails.SelectedItem;
if (apd.ShowDialog() == true)
{
}
When the dialog box opens, my lstReasons list is empty. I don't get any errors and when I place a stop in the code, I see that the reasons collection gets populated with the items from the table.
Reasons needs to be a Property (add { get; set;} ). Also, look at Visual Studio Output - it shows Binding errors, there should be some info about failed binding to reasons.
The problem seems to be How you are creating the property.
I know you put your prperty as an observable collection but this doesn't mean it is by it self observalble!
so you need to notify the UI when this property is changed by doing something in the setter like this:
public ObservableCollection<Twr_POS_Price_Change_Reason> reasons
{
get{....}
set
{
Notify('reasons')
}
}
I don't remember the exact code because I didn't use WPF for a while but it is a method in INotifyPropertyChanged, good luck!
It seems your binding path is set to POS_Price_Change_Reason, while the name of your property is reasons. Unless you didn't include POS_Price_Change_Reason in your example code and reasons is the backing field for this property.
Also, keep in mind that you can only bind to public properties, not fields. Additionally, if you change the value of the property, you need to notify the view of this change, by invoking your PropertyChangedEventHandler event for that property:
PropertyChanged(new PropertyChangedEventArgs("YourPropertyName"));

Binding with ElementName in the nested UserControls

I have the following simple code:
<Window x:Class="WpfApplication3.MainWindow"
x:Name="WindowInst" …>
<local:UserControl1/>
</Window>
<UserControl x:Class="WpfApplication3.UserControl1" …>
<Button Content="Click me"
Command="{Binding DataContext.ButtonClickedCommand,
ElementName=WindowInst}" Height="134" Width="314" />
</UserControl>
And in the ViewModel for the Window I have ButtonClickedCommand:
#region Avatar click command
RelayCommand _buttonClickedCommand;
public ICommand ButtonClickedCommand
{
get
{
if (_buttonClickedCommand == null)
{
_buttonClickedCommand = new RelayCommand(() => this.ButtonClicked());
}
return _buttonClickedCommand;
}
}
public void ButtonClicked()
{
}
#endregion
Unfortunately, it causes exception at runtime:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=WindowInst'. BindingExpression:Path=DataContext.ButtonClickedCommand; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')
Could you explain me what’s wrong with it?
Try modifying your binding as follows...
<Window x:Class="WpfApplication3.MainWindow"
x:Name="WindowInst" …>
<local:UserControl1/>
</Window>
<UserControl x:Class="WpfApplication3.UserControl1" …>
<Button Content="Click me"
Command="{Binding Path=ButtonClickedCommand, Mode=FindAncestor, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" Height="134" Width="314" />
</UserControl>
This should work as WindowInst does not live within Self since your container is the UserControl; which is being placed within the Window. In addition you need to make sure that you are setting your DataContext within the Window or its value will be null and no binding will ever occur no matter if your syntax is accurate or not.
Your bindings are a little off.
Please see this tutorial on WPF command binding.
As a general rule, specify as little as possible in your bindings. I don't think you need element name in this circumstance and datacontext is the assumed root of your bindings.

Categories