WPF: TextBox bind to "." and setter call - c#

I have a simple WPF application. In the code behind I have an InputFile property like this.
public string InputFile
{
get
{
return _inputFile;
}
set
{
_inputFile = value;
OnPropertyChanged("InputFile");
}
}
Inside the XAML I have a StackPanel like this:
<StackPanel Orientation="Horizontal" DataContext="{Binding Path=InputFile}">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding Path=.}"></TextBox>
<Button Command="Select">...</Button>
<Button Command="Open">-></Button>
</StackPanel>
I need to keep the stackpanel datacontext linked to InputFile to allow the commands functions to access it.
Problem: when InputFile changes, the TextBox is updated but if I type a new value in the Textbox the property InputFile is not updated (setter method is not called). Any idea?

You can try the following:
<TextBox Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}"></TextBox>

If your commands are the main reason to have the DataContext set like this, what about this solution?
<StackPanel Orientation="Horizontal">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding InputFile}"></TextBox>
<Button Command="Select" CommandParameter="{Binding InputFile}">...</Button>
<Button Command="Open" CommandParameter="{Binding InputFile}">-></Button>
</StackPanel>
if all else fails, this should always work. Its a nasty workaround but it should get the job done:
<Grid x:Name="myGrid">
StackPanel Orientation="Horizontal" DataContext="{Binding Path=InputFile}">
<StackPanel.CommandBindings>
<CommandBinding Command="Open"
CanExecute="OpenCanExecute"
Executed="OpenExecuted" />
<CommandBinding Command="Select"
CanExecute="SelectCanExecute"
Executed="SelectExecuted" />
</StackPanel.CommandBindings>
<TextBox Text="{Binding ElementName=myGrid, Path=DataContext.InputFile}"></TextBox>
<Button Command="Select">...</Button>
<Button Command="Open">-></Button>
</StackPanel>
</Grid>
Like Tomtom already wrote, you might need the UpdateSourceTrigger.

In your binding you can specify the mode to be two way. This will call your setter when the text box value changes from the UI.
<TextBox Text="{Binding Path=., Mode=TwoWay}"/>

You can bind directly to TextBox.
What is the point to bind to StackPanel if InputFile is string?
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding InputFile}"></TextBox>
</StackPanel>
Edit:
You can bind command by using FindAncestor:
Command="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.Select}"
Alternatively you can create custom type similar to this one:
public class InputFileViewModel
{
string File {get; set;}
ICommant Select {get; set;}
}
And then bind in TextBox to File property.

Related

How can I bind a button to a viewmodel command from within a data template that's in Page.Resources?

In my app using the Windows App SDK, I'm trying to get a button-tapped event to trigger a command in my viewmodel. I have gotten this to work when the data template is between the and on the page, but it won't work when the data template is in Page.Resources. I have to place some data templates in Page.Resources because I need to have multiple data templates in the TreeView and use a DataTemplateSelector to select the correct one based on the type of the item. I've gotten the template selection working, but I just can't get the button binding to work. It looks like the ElementName binding can't find the page name. Can anyone show me what I'm doing wrong?
<Page.Resources>
<DataTemplate x:Key="treeViewSFTemplate" x:DataType="models:SF">
<TreeViewItem ItemsSource="{Binding OwnedSFEs}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=sfSettingsPage, Path=ViewModel.AddNewSubfactorCommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<TreeViewItem>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</TreeViewItem>
</DataTemplate>
</Page.Resources>
And then in the in the page code below:
<TreeView x:Name="MyTreeView"
ItemsSource="{x:Bind ViewModel.SFList}"
ItemTemplateSelector="{StaticResource treeViewDataTemplateSelector}" />
The reason for this behavior is that you wrapped a TreeViewItem in
the DataTemplate. It will cause that TreeViewItem contains sub-TreeViewItem in the Visual Tree and you could not access correct DataContext with element name in your button. Please remove TreeViewItem from your code.
Like:
<DataTemplate x:Key="treeViewSFETemplate" x:DataType="models:SFE">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding SFEName}"/>
<Button Content="+">
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding ElementName=thisPage, Path=ViewModel.DeleteSFECommand}"/>
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</Button>
<Button Content="-"/>
</StackPanel>
</DataTemplate>
Assuming the view model MyViewModel for the page is bound to the DataContext property of the page and MyItemViewModel is the datatype of the item in a collection. The CommandParameter binding binds the view model of the item:
<Page
...
x:Name="MyPage"
...
<Page.Resources>
...
<DataTemplate x:Key="MyDataTemplate" x:DataType="vm:MyItemViewModel>
...
<Button
...
Command="{Binding ElementName=MyPage, Path=DataContext.MyCommand}"
CommandParameter="{Binding}"
...
Changes to the value of the property DataContext can be verified by adding an eventhandler after InitializeComponent() in the page constructor:
public MyPage()
{
InitializeComponent();
...
DataContextChanged += MyPage_DataContextChanged;
...
}
void MyPage_DataContextChanged(FrameworkElement sender, DataContextChangedEventArgs args)
{
if (DataContext is MyViewModel vm)
{
}
}

How to bind button to a delegated command?

I have a list that looks like as follows. In the list I want buttons that do something through a delegated command.
<dxe:ListBoxEdit
Grid.Row="0"
Grid.Column="5"
Margin="0,50,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
DisplayMember="Name"
ItemSource="{Binding Items}"
ItemTemplate="{StaticResource ItemTemplate}"
Name="lista"
SelectionMode="Single"
ScrollViewer.CanContentScroll="True">
</dxe:ListBoxEdit>
and dataTemplate look like this:
<Window.Resources>
<DataTemplate x:Key="ProductsTemplate" >
<dxe:ListBoxEditItem>
<DockPanel>
<Image Source="{Binding Picture}" HorizontalAlignment="Left" Margin="25 50"/>
<TextBlock FontSize="14" TextWrapping="Wrap">
<TextBlock.Text >
<MultiBinding StringFormat="{}{0} Price: {1} TaxRateLevel: {2} ">
<Binding Path="Name" />
<Binding Path="Price" />
<Binding Path="TaxRateLevel" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Content="Add" Command ="{Binding AddCommand}"></Button>
</DockPanel>
</dxe:ListBoxEditItem>
</DataTemplate>
</Window.Resources>
If you are using MVVM (Model-View-ViewModel) you can achieve the result with RelayCommand.
In your ViewModel file, you need to declare your command:
public RelayCommand YourCommand { get; set; }
Then, you need to initialize it (in the view model constructor for example):
YourCommand = new RelayCommand(YourMethodName);
Finally, for the XAML, I have this example of a ListBox that you can adapt for your specific situation:
<ListBox
x:Name="Files"
ItemsSource="{Binding Items, Mode=TwoWay}"
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding
Gesture="LeftDoubleClick"
Command="{Binding YourCommand }"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As you can see in this example, we have a ListBox that is binding some data. To set the command, we access TextBlock inside ListBox.ItemTemplate and add the command for the gesture LeftDoubleClick.
If the ViewModel is set to the Window's DataContext, and the AddCommand is set at the main level of the ViewModel, then the command must know what elements it works with.
That is, the command necessarily needs parameter processing.
The data required for the command is retrieved from the parameter.
How exactly this is done depends on the framework you use.
For binding, you can do this:
Give a name to the Window <Window x:Name="main"....
Bind to the DataContext of the Window using binding of the ElementName type and passing the current element in the parameter:
XAML:
<Button Content="Add"
Command ="{Binding DataContext.AddCommand, ElementName=main}"
CommandParameter="{Binding}"/>
Also, the layout of the elements is not clear from your question.
In the ListBox, you are using a template named "ItemTemplate" for the item, but you didn't show it.
You are showing the "ProductsTemplate" template, but where it is used is not clear.

TemplateColumn button command binding not working in Syncfusion DataGrid

I'm using Syncfusion's datagrid in a UWP project. Everything works fine except command binding of button inside a GridTemplateColumn. What could be the issue ?
XAML
<my:GridTemplateColumn MappingName="Actions" HeaderText="Actions"
AllowFiltering="False">
<my:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="0" HorizontalAlignment="Center">
<Button VerticalAlignment="Center" Width="40" Content="Delete"
my:FocusManagerHelper.FocusedElement="True"
Command="{x:Bind ViewModel.RemoveBtnCommand}"/>
</StackPanel>
</DataTemplate>
</my:GridTemplateColumn.CellTemplate>
</my:GridTemplateColumn>
View Model
public ICommand RemoveBtnCommand { get; }
public HRMDepartmentsViewModel()
{
IsActive = true;
RemoveBtnCommand = new AsyncRelayCommand(CommandRemoveBtnClickedExecute);
}
private async Task CommandRemoveBtnClickedExecute()
{
// never executed-- code here
}
What else i've tried
I tried to use Binding instead if x:Bind but it also doesn't work
Command="{Binding Path=RemoveBtnCommand}" CommandParameter="{Binding}"
Observation
If i don't use Command and use Click event binding it works.
<my:GridTemplateColumn MappingName="Actions" HeaderText="Actions"
AllowFiltering="False">
<my:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Spacing="0" HorizontalAlignment="Center">
<Button VerticalAlignment="Center" Width="40" Content="Delete"
Click="{x:Bind ViewModel.ButtonBase_OnClick}"/>
</StackPanel>
</DataTemplate>
</my:GridTemplateColumn.CellTemplate>
</my:GridTemplateColumn>
View Model
public void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// It gets called
}
TemplateColumn button command binding not working in Syncfusion DataGrid
I'm afraid you can't bind the button command in the DataTemplate like above, because command will not find correct DataContext. If you want to bind the ViewModel's RemoveBtnCommand, you need get root DataContext then direct to RemoveBtnCommand property. For more please refer the following code.
<Page.DataContext>
<local:MainViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<Custom:SfDataGrid x:Name="MyDataGrid" ItemsSource="{Binding Orders}">
<Custom:SfDataGrid.Columns>
<Custom:GridTemplateColumn
AllowFiltering="False"
HeaderText="Actions"
MappingName="Actions"
>
<Custom:GridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal"
Spacing="0"
>
<TextBlock Text="{Binding Name}" />
<Button
Width="40"
VerticalAlignment="Center"
Custom:FocusManagerHelper.FocusedElement="True"
Command="{Binding ElementName=MyDataGrid, Path=DataContext.RemoveBtnCommand}"
Content="Delete"
/>
</StackPanel>
</DataTemplate>
</Custom:GridTemplateColumn.CellTemplate>
</Custom:GridTemplateColumn>
</Custom:SfDataGrid.Columns>
</Custom:SfDataGrid>
</Grid>
You can achieve your requirement by pass the row information as a parameter to the command in ViewModel when clicking a button in TemplateColumn by passing the CommandParameter. Please refer the below KB documentation for more details reference,
KB Link:https://www.syncfusion.com/kb/5910/how-to-pass-row-data-as-parameter-to-the-command-in-viewmodel-when-clicking-the-button-in
Regards,
Vijayarasan S

Bind keypress event to ListViewItem in WPF

I have a ListView which populates the view with ListViewItems containing an image and text(file browser). How can I fire a Command when the user presses the 'Enter' key on a selected item while respecting the MVVM design pattern? I've searched and found a few solutions, but none of them seem to work for me.
<ListView ScrollViewer.HorizontalScrollBarVisibility="Hidden"
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.ScrollUnit="Item"
Background="#fdfaf4"
Name="filesView"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- The image and item name -->
<Grid Width="{Binding ActualWidth, ElementName=filesView, Converter={x:Static converter:GridWidthToListViewWidthConverter.Instance}}"
Background="Transparent">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.07*" MinWidth="25" MaxWidth="40" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Drive, file or folder -->
<Image Grid.Column="0"
Margin="0,0,5,0"
Name="itemType"
Source="{Binding Type,
Converter={x:Static converter:HeaderToImageConverter.Instance}}" />
<!-- The text is binded to the image size, so they'll expand/shrink together -->
<TextBlock Grid.Column="1"
VerticalAlignment="Center"
FontSize="{Binding ActualHeight,
ElementName=itemType, Converter={x:Static converter:ImageSizeToFontSizeConverter.Instance}}"
Text="{Binding Name}" />
<!-- The command to enter a drive/folder is called from here -->
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding EnterCommand, Mode=TwoWay}" />
<KeyBinding Key="Enter" Command="{Binding EnterCommand, Mode=TwoWay}" />
</Grid.InputBindings>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The MouseBinding works just fine. I've tried putting the KeyBinding in the ListView instead of the grid and getting the focused item with the SelectedItem property but still nothing.
Implement the PreviewKeyDown event for the root Grid in the ItemTemplate or the ListViewItem container in the code-behind of the view and simply execute the command from there, e.g.:
private void ListViewItem_PreviewKeyDown(object sender, KeyEventArgs e)
{
var viewModel = DataContext as YourViewModel;
viewModel.YourCommand.Execute(null);
}
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="PreviewKeyDown" Handler="ListViewItem_PreviewKeyDown" />
</Style>
</ListView.ItemContainerStyle>
Or implement a behaviour that hooks up the event handler and does the same: https://www.codeproject.com/Articles/28959/Introduction-to-Attached-Behaviors-in-WPF.
Neither approach breaks the MVVM pattern since you are invoking the exact same view model command from the exact same view that the XAML markup is a part of.
MVVM is not about eliminating code from the views, it is about separation of concerns. If you invoke the command using a KeyBinding or an event handler doesn't matter.
Try Gesture="Enter";
<Grid.InputBindings>
<KeyBinding Gesture="Enter" Command="{Binding EnterCommand}" />
</Grid.InputBindings>

How to capture dynamic button clicks in ViewModel

I have created a bunch of buttons by binding an ObservableCollection<module>.
In my ViewModel I would like to capture the click event.
For buttons I usually use:
RelayCommand launchCommand;
public ICommand LaunchCommand{
get{
if (launchCommand == null){
launchCommand = new RelayCommand(LaunchCommandExecute, CanLaunchCommandExecute);
}
return launchCommand;
}
}
private void LaunchCommandExecute(object parameter){
//Do something to recognize the button.
//Could use ObservableCollection<Module> module_objects
//to match, if I could get the buttons content or name
}
private bool CanLaunchCommandExecute(object parameter){
return true;
}
In LaunchCommandExecute I have placed a couple of thoughts. I would be interested in what object parameter holds? Is it anything useful to me?
The button has the following bindings which I could use to match up:
<Button Tag="{Binding ModuleName}" Content="{Binding ModuleAbbreviation}" Command="{Binding LaunchCommand}" IsEnabled="{Binding ModuleDisabled}" Style="{DynamicResource LauncherButton}" Background="{Binding ModuleColor}" />
Does anyone know how to do this?
[EDIT] This is after accepting the answer below
What I am finding is that LaunchCommand is not firing. I was wondering if anything in the below code is conflicting?
<UserControl.DataContext>
<viewmodel:LauncherViewModel />
</UserControl.DataContext>
<Grid >
<ItemsControl ItemsSource="{Binding Source={x:Static m:ModuleKey._module_objects}}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<my:AlignableWrapPanel HorizontalAlignment="Stretch" Name="alignableWrapPanel1" VerticalAlignment="Center" HorizontalContentAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Button Content="{Binding ModuleAbbreviation}" Command="{Binding LaunchCommand}" CommandParameter="{Binding ModuleName}" IsEnabled="{Binding ModuleDisabled}" Style="{DynamicResource LauncherButton}" Background="{Binding ModuleColor}" FontSize="32" FontFamily="Tahoma" Width="130" Height="100" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
[EDIT Answer] Nevermind thought about what I was trying to do and found that the command could not see the correct DataContext. Adding the following sorted it:
Command="{Binding DataContext.LaunchCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
The parameter is set by CommandParameter. In this case, all you have to do is bind it to "ModuleName":
<Button Command="{Binding LaunchCommand}" CommandParameter="{Binding ModuleName}" ...
Pick it up using a cast - assuming it's a string:
private void LaunchCommandExecute(object parameter){
string moduleName = parameter as string;
// ...
}
(Note that you could also set CommandParameter to the Button's Tag or Content by using {Binding RelativeSource={RelativeSource Self},Path=Tag}, but that would be a round-about approach in this case.)

Categories

Resources