I wnat to make a kind of search textbox when the user would input some text to the texbox it will search for him, therefore I want to know what the textbox content, for now I have:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
...
<TextBox Text="{Binding Search}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding SearchCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
At the viewModel:
public ICommand SearchCommand { get; private set; }
public MyViewModel()
{
SearchCommand = new RelayCommand(SearchMethod);
}
void SearchMethod()
{
if(Search==null)
MessageBox.Show("Search text is null");
}
string search;
public string Search
{
get { return search; }
set
{
Set(() => Search, ref search, value);
RaisePropertyChanged("Search");
}
}
But every time that I input some text to the textbox it shows the message: Search text is null
By default, the binding is evaluated when the TextBox loses focus. Your event is fired anytime a key is pressed.
To change this behavior, you can set the UpdateSourceTrigger in the Binding:
<TextBox Text="{Binding Search, UpdateSourceTrigger=PropertyChanged}" />
Related
I am using a Listbox in m WPF app where I tried to remove the SelectedItem so the user can reclick on it to do an action.
I have a classic ListBox :
<ListBox
x:Name="MenuItemList"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding MenuItems}"
SelectedItem="{Binding SelectedMenu, UpdateSourceTrigger=PropertyChanged}">
[...] </ListBox>
I have binded the SelectedMenu in my VM :
public MenuItem SelectedMenu
{
get { return null; }
set
{
MenuIsOpened = false;
DisplayedMenu = value;
OnPropertyChanged("SelectedMenu");
}
}
I tried another way with a private property where I changed it to null
private MenuItem _SelectedMenu;
public MenuItem SelectedMenu
{
get { return _SelectedMenu; }
set
{
MenuIsOpened = false;
DisplayedMenu = value;
_SelectedMenu = null;
OnPropertyChanged("SelectedMenu");
}
}
But it does not work as I want... When I click on an item, the property is null but in the view, the listbox always highlights the selected item and the second click on it doesn't trigger the property.
Heres my working example:
// in constructor or so
AddedBlocks = new ObservableCollection<string>();
// the property
public ObservableCollection<string> AddedBlocks { get; }
/// <summary>
/// Deletes selected block from view and model
/// </summary>
private void OnDeleteSelectedBlock(object parameter)
{
try
{
AddedBlocks.RemoveAt(selectedBlockIndex);
}
}
And XAML:
<ListBox ItemsSource="{Binding Blocks, Mode=OneWay}" SelectedIndex="{Binding SelectedBlockIndex, Mode=TwoWay}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete block" Command="{Binding DeleteSelectedBlock}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
See the two way binding on the selectedIndex. Maybe its easier to use that instead of selectedItem.
Hope this helps. If you dont want to use the context menu, add a button or this
<KeyBinding Key="Delete" Command="{Binding DeleteSelectedBlock}"/>
I do something else to resolve my problem.
I didn't try to change the selected item but I added a command on my Listbox
<ListBox
x:Name="MenuItemList"
Grid.Row="1"
ItemsSource="{Binding MenuItems}"
SelectedItem="{Binding SelectedMenu, UpdateSourceTrigger=PropertyChanged}">
[...]
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding CloseMenuCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
And in my MVVM, I close my menu as I want to do it at the beginning :
private void ExecuteCloseMenuCommand(object o)
{
MenuIsOpened = false;
}
In this way, the user can reclick on the item menu which is already selected and the menu will still be closed by the click.
I am using 2 textboxes. If the user types something into one of the textboxes, the other textbox will be disabled. If the user deletes all text in one of the textboxes, the other textbox will be re-enabled.
These rules are to make sure that only one textbox can contain text.
The textbox with text inside is the search textbox that listens to the search button trigger "Suchen".
Here is how the view looks like:
In order for these rules to work I want to use the TextChanged-Events as ICommands according to MVVM standards. I gave this a try but it doesn't do what I want it to.
What does it do? - If I type something inside the "Artikelbezeichnung"-textbox, the "Lieferant"-textbox won't disable and if I delete all text inside "Artikelbezeichnung", the "Lieferant"-textbox will disable (and never re-enable). I believe that I cannot grasp the logic of this strange behaviour and that is why I need your help. I reduced the code to a minimum to make things easier for you.
What do I need to change to make my rules work?
Please take a look at the following code and help me out. Thanks a lot for trying!
XAML-View
<StackPanel Height="423" VerticalAlignment="Bottom">
<Label Name="lblArtikelbezeichnung" Content="Artikelbezeichnung:" Margin="20, 20, 20, 0"></Label>
<TextBox Name="txtArtikelbezeichnung"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding BezEnabled}"
Text="{Binding BezText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding TextChangedBez}" />
</i:EventTrigger>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding KeyUpBez}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<!--TextChanged="txtArtikelbezeichnung_TextChanged"
KeyUp="txtArtikelbezeichnung_KeyUp"-->
<Label Name="lblLieferant" Content="Lieferant:" Margin="20, 0, 20, 0"></Label>
<TextBox Name="txtLieferant"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding LiefEnabled}"
Text="{Binding LiefText}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:InvokeCommandAction Command="{Binding TextChangedLief}" />
</i:EventTrigger>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding KeyUpLief}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<!--TextChanged="txtLieferant_TextChanged"
KeyUp="txtLieferant_KeyUp"-->
<Button Name="btnSuchen"
Content="Suchen"
Width="100" Height="25"
Margin="20, 10,240, 10"
Command="{Binding GefilterteSuche}">
</Button>
...
<StackPanel>
Code Behind
using System.Windows;
namespace Lieferscheine
{
/// <summary>
/// Interaktionslogik für artikelHinzu.xaml
/// </summary>
public partial class artikelHinzu : Window
{
public artikelHinzu()
{
InitializeComponent();
DataContext = new ArtikelHinzuViewModel();
}
}
}
View Model
public class ArtikelHinzuViewModel : INotifyPropertyChanged
{
//ICommands
public ICommand TextChangedLief => new DelegateCommand<object>(TextChangedLieferant);
public ICommand TextChangedBez => new DelegateCommand<object>(TextChangedBezeichnung);
private bool _bezEnabled = true;
private bool _liefEnabled = true;
public bool BezEnabled
{
get
{
return _bezEnabled;
}
set
{
_bezEnabled = value;
OnPropertyChanged("BezEnabled");
}
}
public bool LiefEnabled
{
get
{
return _liefEnabled;
}
set
{
_liefEnabled = value;
OnPropertyChanged("LiefEnabled");
}
}
private string _bezText;
private string _liefText;
public string LiefText
{
get
{
return _liefText;
}
set
{
_liefText = value;
OnPropertyChanged("LiefText");
}
}
public string BezText
{
get
{
return _bezText;
}
set
{
_bezText = value;
OnPropertyChanged("BezText");
}
}
public void TextChangedBezeichnung(object param)
{
if (!String.IsNullOrWhiteSpace(BezText))
{
LiefEnabled = false;
}
else
{
LiefEnabled = true;
}
}
public void TextChangedLieferant(object param)
{
if (!String.IsNullOrWhiteSpace(LiefText))
{
BezEnabled = false;
}
else
{
BezEnabled = true;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
//Konstruktor
public ArtikelHinzuViewModel()
{
}
}
I believe the undesired behavior is caused by Event Racing.
Note that the data binding mechanism, by default, calls the setter of Text property upon LostFocus of binding target (UI element). But TextChanged Event had been fired before the TextBox lose focus. This causes your command fail to yield the correct logic.
A quick solution will be
Text="{Binding BezText, UpdateSourceTrigger=PropertyChanged}">
Of course I don't know your exact situation, but I don't think it is necessary to use ICommand and System.Windows.Interactivity even in a MVVM sense. You might consider the following:
ViewModel
public string LiefText
{
get
{
return _liefText;
}
set
{
_liefText = value;
OnPropertyChanged("LiefText");
if (!String.IsNullOrWhiteSpace(_liefText))
BezEnabled = false;
else
BezEnabled = true;
}
}
public string BezText
{
get
{
return _bezText;
}
set
{
_bezText = value;
OnPropertyChanged("BezText");
if (!String.IsNullOrWhiteSpace(_bezText))
LiefEnabled = false;
else
LiefEnabled = true;
}
}
View
<TextBox Name="txtArtikelbezeichnung"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding BezEnabled}"
Text="{Binding BezText, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding KeyUpBez}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<TextBox Name="txtLieferant"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding LiefEnabled}"
Text="{Binding LiefText, UpdateSourceTrigger=PropertyChanged}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyUp">
<i:InvokeCommandAction Command="{Binding KeyUpLief}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
I have a ListBox;
<ListBox Grid.Row="1" Grid.ColumnSpan="2"
x:Name="customerListBox"
ItemsSource="{Binding Customers}"
DisplayMemberPath="Customername"
SelectionMode="Single" Width="200"/>
Customers is public ObservableCollection<Customer> Customers { get; private set; }
Now I am binding ListBox Selected Item to a Text Box:
<TextBox Text="{Binding ElementName=customerListBox,
Path=SelectedValue.Customername,Mode=OneWay}"/>
I have made it one-way as there I want to commit the changes only on click of my Save button and not when the value change on TextBlock.
<Button Content="Save" Grid.Column="0" Grid.Row="3" Width="80" Height="30"
Command="{Binding SaveCommand}"
You're going the wrong way about it, imho.
Don't bind TextBox directly against selected item. Rather, create a new command, SelectionChangedCommand, and new property, CurrentlyActiveText, bind it against TextBox.
The logic would be simple:
SelectionChangedCommand = new RelayCommand(selectedItem=> {
// todo: ask user if he wants to commit the previous changes?!
CurrentlyActiveText = (string)selectedItem;
})
SaveCommand = new RelayCommand(() => {
yourObservable[SelectedIndex] = CurrentlyActiveText;
});
Perhaps a nicer way of doing this is using Triggers to fire a Command on the ListBox SelectionChanged event. Putting logic in a property setter always feels a bit wrong to me
<ListBox...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CustomerListBoxSelectionChanged}" CommandParameter="{Binding ElementName=customerListBox,Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
In your view model:
public Customer CurrentCustomer { get; set; }
public RelayCommand<Customer> CustomerListBoxSelectionChanged { get; set; }
private void OnCustomerListBoxSelectionChanged(Customer customer)
{
CurrentCustomer = customer;
NewCustomerName = customer.CustomerName;
}
private string _newCustomerName;
public string NewCustomerName
{
get { return _newCustomerName; }
set
{
if (_newCustomerName == value)
return;
_newCustomerName = value;
RaisePropertyChanged("NewCustomerName");
}
}
Your TextBox in your XAML becomes:
<TextBox Text="{Binding NewCustomerName}"/>
And finally, your SaveCommand calls a method that simply does...
private void OnSave()
{
CurrentCustomer.CustomerName = NewCustomerName;
}
Note you will also need to make sure that your CustomerName in your Customer object is raising PropertyChanged events in order to reflect the update in your ListBox
Note that doing it this way also saves you a futher lookup the ObservableCollection to perform an update. It'll save you some time - any performance gain is always good :)
I develop application with using MVVM pattern. I using MVVMLight library to do this. So if I need to handle TextBox TextChange event I write in XAML:
<I:EventTrigger EventName="TextChanged">
<I:InvokeCommandAction Command="{Binding PropertyGridTextChange}"/>
</I:EventTrigger>
where PropertyGridTextChange is Command in ViewModel. But TextBox has no Paste event!
This solution only works if application don't use MVVM pattern, because you need to have link on TextBox.
<DataTemplate x:Key="StringTemplate">
<TextBox Text="{Binding Value, ValidatesOnDataErrors=True, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</TextBox>
</DataTemplate>
Important detail - TextBox placed within DataTemplate.
I have no idea how can I handle "paste event".
I want PasteCommand to be invoked when I paste text into TextBox. And I need that TextBox.Text or TextBox itself to be passed as parameter into PasteCommandMethod.
private RelayCommand<Object> _pasteCommand;
public RelayCommand<Object> PasteCommand
{
get
{
return _pasteCommand ?? (_pasteCommand =
new RelayCommand<Object>(PasteCommandMethod));
}
}
private void PasteCommandMethod(Object obj)
{
}
I can suggest answer on my question.
Class-helper.
public class TextBoxPasteBehavior
{
public static readonly DependencyProperty PasteCommandProperty =
DependencyProperty.RegisterAttached(
"PasteCommand",
typeof(ICommand),
typeof(TextBoxPasteBehavior),
new FrameworkPropertyMetadata(PasteCommandChanged)
);
public static ICommand GetPasteCommand(DependencyObject target)
{
return (ICommand)target.GetValue(PasteCommandProperty);
}
public static void SetPasteCommand(DependencyObject target, ICommand value)
{
target.SetValue(PasteCommandProperty, value);
}
static void PasteCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)sender;
var newValue = (ICommand)e.NewValue;
if (newValue != null)
textBox.AddHandler(CommandManager.ExecutedEvent, new RoutedEventHandler(CommandExecuted), true);
else
textBox.RemoveHandler(CommandManager.ExecutedEvent, new RoutedEventHandler(CommandExecuted));
}
static void CommandExecuted(object sender, RoutedEventArgs e)
{
if (((ExecutedRoutedEventArgs)e).Command != ApplicationCommands.Paste) return;
var textBox = (TextBox)sender;
var command = GetPasteCommand(textBox);
if (command.CanExecute(null))
command.Execute(textBox);
}
}
Using in XAML. In TextBox as attribute.
TextBoxPasteBehavior.PasteCommand="{Binding PropertyGridTextPasted}"
PropertyGridTextPasted - Command in the ViewModel.
I've been struggling with this kind of problem too in recent days. My first approach would be to have a property in the VM that is bound to the text box (which I am sure you already have). Then bind an ICommand to an event to handle the on paste event:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<i:Interaction.Triggers>
<i:EventTrigger EventName="RowEditEnding">
<i:InvokeCommandAction Command="{Binding DocRowEdit}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
you need to define the namespace in the proper part of the XAML code, and then put the interaction triggers in as part of the textbox definition. Here I am capturing the RowEditEnding event to do some stuff similar to what you are attempting.
The command binding is another piece, let me know if you need more information on how that needs to be set up.
Hi am pretty new to WPF and I have started to implement an app using mvvm pattern based on the mvvm light framework. I found it great but I got a problem using two EventToCommand on controls that are supposed to interact together. I am guessing I am doing something wrong... would you help me to find out what exactly?
I have one window with two controls: a combo box which allows the selection of a Name and a TextBox that displays a Caption. A name has a default caption (hardcoded for the example in the constructor of the ViewModel, see below). So when the user select a name the textbox should display the default caption. However, the caption is editable, which means that the user can change the Caption (as long as he does not change the Name again).
In the example below, I have implemented this using MVVM pattern with MVVM Light framework.
The Ok button, is only bound to a command that logs the value in the OutPut window (to see properties values in the ViewModel).
As you will see in the source code comment, the problem comes from the fact that NameSelectionChanged command triggers the CaptionTextChanged command with an "outdated value". For now, I implemented a hacky workaround (not in the code below) by setting a boolean value that ignores the code in CaptionTextChanged when executing the RaisePropertyChanged in NameSelectionChanged but it is not really satisfactory.
the View in XAML
<Window x:Class="TwoControls.MainWindow"
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"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:TwoControls"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<vm:DummyViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</Window.Resources>
<Window.DataContext>
<Binding Path="GetViewModel" Source="{StaticResource Locator}" />
</Window.DataContext>
<Grid>
<ComboBox ItemsSource="{Binding ColumnNames}" x:Name="NamesComboBox" HorizontalAlignment="Left" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand Command="{Binding NameSelectionChanged}" CommandParameter="{Binding SelectedValue, ElementName=NamesComboBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<TextBox Text="{Binding Caption, Mode=TwoWay}" Name="CaptionTextBox" HorizontalAlignment="Left" Height="23" Margin="0,45,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<cmd:EventToCommand Command="{Binding CaptionTextChanged}" CommandParameter="{Binding Text, ElementName=CaptionTextBox}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<Button Content="Ok" Command="{Binding ClickOk}" HorizontalAlignment="Left" Margin="120,170,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
the view model in C#
public class MainViewModel : ViewModelBase
{
private readonly List<string> _names;
private readonly string[] _captions;
public MainViewModel()
{
_names = new List<string>(new[]{"TOTO","TATA","TUTU"});
_captions = new[] {"toto", "tata", "tutu"};
}
public string Name { get; set; }
public string Caption { get; set; }
public ICommand NameSelectionChanged
{
get
{
return new RelayCommand<string>((input) =>
{
Name = input;
int index = _names.IndexOf(input);
Caption = _captions[index];
//Trigger the execution of CaptionTextChanged with the old value of the TextBox
//even if Caption and TextBox.Text are bound TwoWay....
base.RaisePropertyChanged(()=>this.Caption);
});
}
}
public ICommand CaptionTextChanged
{
get
{
return new RelayCommand<string>((input) =>
{
Caption = input;
});
}
}
public ICommand ClickOk
{
get
{
return new RelayCommand(() =>
{
Console.WriteLine("Name=" + Name +";" +"Caption=" + Caption);
});
}
}
public List<string> ColumnNames
{
get { return _names; }
}
}
Ps: targeted .NET is 3.5 and MVVMLight's version is 4.1.27.1
You don't need to use any events to do this in WPF. All this is possible by just using property Bindings if you implement the INotifyPropertyChanged interface on your view model as is customary... how about this:
private ObservableCollection<string> columnNames = new
ObservableCollection<string>();
public ObservableCollection<string> ColumnNames
{
get { return columnNames; }
set { columnNames = value; NotifyPropertyChanged("ColumnNames"); }
}
private string selectedColumnName;
public string SelectedColumnName
{
get { return selectedColumnName; }
set
{
selectedColumnName = value;
NotifyPropertyChanged("SelectedColumnName");
int index = _names.IndexOf(value); // <<< Changes are reflected here
Caption = _captions[index];
}
}
private string caption = string.Empty;
public string Caption
{
get { return caption; }
set { caption = value; NotifyPropertyChanged("Caption"); }
}
In XAML:
<Grid>
<ComboBox ItemsSource="{Binding ColumnNames}" SelectedItem="{Binding
SelectedColumnName}" x:Name="NamesComboBox" HorizontalAlignment="Left"
VerticalAlignment="Top" Width="120" />
<TextBox Text="{Binding Caption, Mode=TwoWay}" Name="CaptionTextBox"
HorizontalAlignment="Left" Height="23" Margin="0,45,0,0" TextWrapping="Wrap"
VerticalAlignment="Top" Width="120" />
<Button Content="Ok" Command="{Binding ClickOk}" HorizontalAlignment="Left"
Margin="120,170,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
Let me know how it goes.