How could I propagate item's Validation.Error up to ItemsControl? - c#

WPF has turned on Validation in TextBox by default. How could I propagate a TextBox's Validation.Error up to its ItemsControl if its ItemTemplate is composed of the TextBox ? I want to bind a button's IsEnabled into the Validation.Error on an item in ItemsControl.
<ItemsControl ItemsSource="{Binding}"
x:Name="my_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding FirstName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Save">
<Button.IsEnabled >
<Binding ElementName="my_itemsControl"
Path="(Validation.HasError)" />
</Button.IsEnabled>
</Button>

OK, I finally got a nice solution which uses MVVM pattern.
<UserControl x:Class="MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="this_control">
<Button Content="Save"
Command="{Binding Path=SaveCommand}"
CommandParameter="{Binding ElementName=this_control}" />
</UserControl>
And the ICommand Property which I bound to on the view-model is like
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => save(),
param => IsValid((DependencyObject)param) // actually the type of param is MyUserControl
);
}
return _saveCommand;
}
}
where the IsValid is based on the fantastic trick

Related

How to bind a double click to a listbox item

I have two list boxes that both hold collections. The current setup is so that when a item is selected in the left listbox, you can click a button to add that selected state the right listbox. There is an add and remove button for the listboxes that are tied to a custom command with the listbox selected item being the command parameter.
I would like to add a double click functionality to each box so that items can be double clicked to add and remove. I should be able to use my current command execute methods to do this, but have not found a solution to implementing this into a listbox, or listboxitem. I would like to follow MVVM as much as possible, but I've already side stepped that a bit with the current execute methods as i'll show below, but any help would be appreciated. I have not had luck finding anything regarding my specific issue.
<ListBox x:Name="List" ItemContainerStyle="{StaticResource ListBoxItem}" DataContext="{StaticResource VM}"
ItemsSource="{Binding Names, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" DisplayMemberPath="Name"
Style="{StaticResource ResourceKey=ListBox}"/>
<Button Content=">>" Margin="5" Style="{StaticResource ResourceKey=MultiButton}"
CommandParameter="{Binding ElementName=List}"
Command="{Binding Path=AddSelectedItemCommand}"/>
public void AddSelectedItem(object obj)
{
ListBox ListBox = obj as ListBox;
List<Type> List = new List<Type>();
if (Name == null)
Name = new ObservableCollection<Type>();
if (Name != null)
{
foreach (Type item in ListBox.SelectedItems.Cast<object>().ToList())
{
List.Add(item);
Names.Remove(item);
}
foreach (Type listItem in List)
{
var state = Name.FirstOrDefault(aa => aa.Name == listItem.Name);
if (state == null)
{
Name.Add(listItem);
}
}
}
OnPropertyChanged("Name");
OnPropertyChanged("Names");
}
Firstly I would like to let you know that your View Model should know nothing at all about the View itself, so it should know nothing about ListBoxes.
Objects should only know about they things which they depend upon, and not those which depend upon it. Therefore the ViewModel should only know about the collections of data which it is making available to any client.
In your example, what happens when the control is changed from a ListBox -you will have to change your Command.
So, first things first, you will need to change your view model implementation, what you have currently is not MVVM.
Here is an entire listing which should help you along your way:
<Window x:Class="WpfExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfExample"
mc:Ignorable="d"
Title="MainWindow" Height="140" Width="410">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding Path=Names, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedName}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick"
Command="{Binding Path=DataContext.MyOtherDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor} }" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
And the code behind
namespace WpfExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MyViewModel();
}
}
}
Then there is the ViewModel, which you should notice only modifies the collections which are exposed for the View to consume
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Prism.Commands;
namespace WpfExample
{
public class MyViewModel : INotifyPropertyChanged
{
private string _selectedName;
private string _selectedNameTwo;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public ObservableCollection<string> Names { get; }
= new ObservableCollection<string>(new List<string>
{
"Name1",
"Name2",
"Name3",
"Name4",
"Name5"
});
public ObservableCollection<string> NamesTwo { get; } = new ObservableCollection<string>(new List<string>());
public string SelectedName
{
get { return _selectedName; }
set { _selectedName = value; OnPropertyChanged(); }
}
public string SelectedNameTwo
{
get { return _selectedNameTwo; }
set { _selectedNameTwo = value; OnPropertyChanged(); }
}
public ICommand MyOtherDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
NamesTwo.Remove(name);
Names.Add(name);
SelectedNameTwo = "";
});
}
}
public ICommand MyDoubleClickCommand
{
get
{
return new DelegateCommand<string>(name =>
{
Names.Remove(name);
NamesTwo.Add(name);
SelectedName = "";
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I have used the Prism.Core package for the DelegateCommand object. This is not essential, I just did it for ease
You don't even need the SelectedName and SelectedNameTwo properties if they will not be used whilst processing the ViewModel. I included them for completeness.
.
Edited:
I did not originally notice that this is for a UWP project. I believe the following will work -though it is untested here since I am not set up for UWP on my machine at the moment. I'm not certain of the DoubleClick EventName.
<Page xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core>
<ListBox Grid.Column="1" Margin="10,0,0,0" ItemsSource="{Binding Path=NamesTwo, Mode=OneWay}"
SelectedItem="{Binding Path=SelectedNameTwo}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding .}" >
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleClick">
<core:InvokeCommandAction Command="{Binding Path=DataContext.MyDoubleClickCommand,
RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor} }"
CommandParameter="{Binding .}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Credit to Bill, as the UWP edit pointed me toward a satisfactory solution.
Firstly, I added a NuGet reference to Microsoft.Xaml.Behaviors.Uwp.Managed
Secondly I added the namespaces Bill mentions to the xaml in which my control is located:
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
Then I added some XAML in my control (List View in this example):
<ListView ...>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="DoubleTapped">
<core:InvokeCommandAction Command="{Binding NavigateUpCommand, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
...
</ListView>
In my case, this was a templated control - and the "DoubleTapped" event name was used successfully :)
The Command was set up in the best way I know; made available as an ICommand get accessor on the in the control class, which used a stock "RelayCommand" implementation

Binding was not executed in CommandBinding

i have this xaml code:
<Window.CommandBindings>
<CommandBinding Command="WpfApplication1:MainCommands.Search" Executed="Search"/>
</Window.CommandBindings><Grid>
<StackPanel>
<ListView ItemsSource="{Binding SearchContext}" />
<TextBox Text="{Binding LastName}">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{x:Static WpfApplication1:MainCommands.Search}" />
</TextBox.InputBindings>
</TextBox>
</StackPanel>
The Search-Method looks like:
private void Search(object sender, RoutedEventArgs e)
{
SearchContext = new ObservableCollection<string>(list.Where(element => element.Name == LastName).Select(el => el.Name).ToList());
}
MainCommands:
public static class MainCommands
{
public static RoutedCommand Search = new RoutedCommand();
}
But if i press enter while focus is in textbox, the binding isĀ“nt computet and LastName is Null. What is the reason? How can I avoid this? Or is it possible to explicit call the binding operation?
Thank you in advance.
Set the UpdateSourceTrigger property to PropertyChanged:
<TextBox Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}">
This will cause the source property (LastName) to be set immediately: https://msdn.microsoft.com/en-us/library/system.windows.data.updatesourcetrigger(v=vs.110).aspx
As I can see your Window is a view-model for it. I recommend you to use MVVM and have separate class for view-model where you can place desired ICommand and use CommandParameter on KeyBinding:
<TextBox x:Name="searchBox"
Text="{Binding LastName}">
<TextBox.InputBindings>
<KeyBinding Key="Enter"
Command="{Binding Path=SearchCommand}"
CommandParameter="{Binding Path=Text, ElementName=searchBox}" />
</TextBox.InputBindings>
</TextBox>

How do I get double click edit to work on one row in my list view?

I have a simple list view with gridview to display each row.
I added a key binding for delete which is working fine.
<ListView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteKeyCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}"/>
</ListView.InputBindings>
But when I add a Mousebinding for LeftDoubleClick to edit its not firing the command.
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=LeftDoubleClickCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}" />
After spending the last two hours trying to figure it out the only thing I have come up with is that its firing the double click on the entire list view and not the listview item???
How do I get double click edit to work on one row in my list view? I am using MVVM I don't want to break that so I cant use code behind to hack it. There must be a way to map the command back to my view model.
Update more code:
<ListView x:Name="DatabasesLstVw" ItemsSource="{Binding Path=ClientDetails.Databases}" ItemContainerStyle="{StaticResource alternatingStyle}" AlternationCount="2" Grid.Row="2" Grid.ColumnSpan="4" VerticalAlignment="Top" >
<ListView.InputBindings>
<KeyBinding Key="Delete" Command="{Binding Path=DeleteKeyCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}"/>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding Path=LeftDoubleClickCommand}" CommandParameter="{Binding ElementName=DatabasesLstVw, Path=SelectedItem}" />
</ListView.InputBindings>
As the referenced answer is missing some code, this is how it should be:
public class AddToInputBinding
{
public static System.Windows.Input.InputBinding GetBinding(DependencyObject obj)
{
return (System.Windows.Input.InputBinding)obj.GetValue(BindingProp);
}
public static void SetBinding(DependencyObject obj, System.Windows.Input.InputBinding value)
{
obj.SetValue(BindingProp, value);
}
public static readonly DependencyProperty BindingProp = DependencyProperty.RegisterAttached(
"Binding", typeof(System.Windows.Input.InputBinding), typeof(AddToInputBinding), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
((UIElement)obj).InputBindings.Add((System.Windows.Input.InputBinding)e.NewValue);
}
});
}
Then, in your XAML, you would do something like this:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="ListViewItem">
<Setter Property="local:AddToInputBinding.Binding">
<Setter.Value>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding DataContext.ItemDoubleClick,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}"
CommandParameter="{Binding}"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<ListView ItemsSource="{Binding Patients}">
<ListView.View>
<GridView>
<GridViewColumn Header="Test" />
</GridView>
</ListView.View>
</ListView>
</Grid>
In your viewModel, the command definition would be like this:
RelayCommand<string> _ItemDoubleClick;
public ICommand ItemDoubleClick
{
get
{
if (_ItemDoubleClick == null)
{
_ItemDoubleClick = new RelayCommand<string>(this.ItemDoubleClickExecuted,
param => this.ItemDoubleClickCanExecute());
}
return _ItemDoubleClick;
}
}
private bool ItemDoubleClickCanExecute()
{
return true;
}
private void ItemDoubleClickExecuted(string item)
{
//In item you've got the text of double clicked ListViewItem
}
Note that in this sample, the ListView binded ObservableCollection is of type string. If this was other type, you should change the types in the ICommand definitions. Don't forget also to set the Window DataContext to your ViewModel.
Hope this is clearer now.

UWP/MVVM data binding in listview with a button does not work

I have read articles about how commanding works different inside of a listview so I tried that code but when I click nothing happens. I am using Template10. Most of the example I find are for WPF which has incompatible pieces. Just need the bare minimum to get the button click to call the method below. The relevant parts of my code are :
<ListView x:Name="lvMain"
ItemsSource="{Binding LeadSpeakerItems}"
SelectedItem="{Binding Lsi}">
<ListView.ItemTemplate>
...
<Button Content="Details"
Command="{Binding ElementName=Root, Path=RunCommand}"
Grid.Column="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
And the code:
public ICommand RunCommand { get; private set; }
public MainPageViewModel()
{
if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
{
LeadSpeakerItems.Add(new LeadSpeakerItem {
VelocifyLeadTitle = "The is the lead title that says somrthing about something and her a number 234-456-3454",
VelocifyFirstName = "BobbiMinajobi",
VelocifyLastName = "Luciferdissikusliskus",
VelocifyLoanAmount = 254000.00,
VelocifyHomeValue = 278000.00
});
}
RunCommand = new DelegateCommand<object>(OnRunCommand, CanRunCommand);
}
private void OnRunCommand(object obj)
{
// use the SelectedCustomer object here...
}
private bool CanRunCommand(object obj)
{
return true;
}
EDIT 1:
How would I get that particular item when the button or the listview item is selected? I am trying to get this piece of code run when that happens. I am missing something.
set
{
Set(ref selectedItem, value);
}
Supposing Root is your page or another control with your viewmodel as DataContext, you should alter your XAML to:
<Button Content="Details"
Command="{Binding ElementName=Root, Path=DataContext.RunCommand}"
Grid.Column="1" />
as RunCommand itself is not known to your Root object, but DataContext (your vm) is.
<Button Content="Details"
Command="{Binding RunCommand}"
Grid.Column="1" />
or
<ListView
x:Name="lvMain"
DataContext={Binding}>
....
</ListView>
<Button
DataContext="{Binding ElementName=lvMain, Path=DataContext}"
Content="Details"
Command="{Binding RunCommand}"
Grid.Column="1" />
try use Template10.Mvvm.DelegateCommand
for example
in viewmodel
public ICommand ItemSelected
{
get
{
return new Template10.Mvvm.DelegateCommand<string>((s) =>
{
NavigationService.Navigate(typeof(DetailPage), s);
});
}
}
add to your page
<page
xmlns:Behaviors="using:Template10.Behaviors"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:vm="using:....ViewModel"
....>
<Page.DataContext>
<vm:ViewModel />
</Page.DataContext>
in your listview
<ListView x:Name="listView" ... ItemsSource="{x:Bind ViewModel.ListItem}" >
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="Tapped">
<Core:InvokeCommandAction Command="{x:Bind ViewModel.ItemSelected}" CommandParameter="{Binding ElementName=listView,Path=SelectedItem}"/>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</ListView>

Delete item from ItemControl

My class is has a ObservableCollection of my viewmodel class and I set the itemsource of the Itemcontrol in xaml as below
<ItemsControl ItemsSource="{Binding ConditionItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Background="#FFD0D7EB">
<StackPanel>
<Button Content="Delete" HorizontalAlignment="Right" Width="180" Margin="0,0,12,10" Command="{Binding DeleteItem}" CommandParameter="{Binding}">
</Button> </StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
For some reason my DeleteItem is never called.
private RelayCommand _DeleteRule;
private void DoDeleteRule(object item)
{
if (item != null)
{
MessageBox.Show("in del");
}
}
public ICommand DeleteItem
{
get
{
if (_DeleteRule == null)
_DeleteRule = new RelayCommand(o => DoDeleteRule(o));
return _DeleteRule;
}
}
Am I doing anything wrong in xaml?
The ItemsControl is bound using {Binding ConditionItems}, so it expects the DeleteItem command to be inside the subitems of that list. I guess this is not the case, the DeleteItem exists on the ViewModel.
You could bind to the DataContext of the Window for example, where you can find the DeleteItem command. Or create a proxy element.
I found it. My xaml should be
<Button Content="Delete" Command="{Binding DataContext.DeleteItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}">
</Button>

Categories

Resources