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>
Related
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}" />
I have my MainWindow which has an ItemsControl for my EngineersUserControl (Engineers_UC) along with other controls. My Engineers_UC consists of a few TextBoxes for which I want to add MouseBinding with the aim of being able to left click on a TextBox and another method in my ViewModel to be executed. I have read that the issue might be that the elements of ItemsControl are not focusable but I haven't found a solution. Any ideas ?
MainWindow:
<Grid>
<StackPanel>
<UserControl:Ribbon_UC Loaded="Ribbon_UC_Loaded" Margin="0,0,0,70"/>
<UserControl:Calendar_UC/>
<ItemsControl ItemsSource="{Binding Engineer}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<UserControl:Engineers_UC />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
Engineers_UC:
<TextBox Name="EngineerName" IsReadOnly="True" Style="{StaticResource StyleTrigger} Text ="{Binding FULLNAME}">
<TextBox.InputBindings>
<MouseBinding Command="{Binding EngineerCommand}" CommandParameter="{Binding ElementName=EngineerName}" MouseAction="{Binding EngineerCommand.MouseGesture}"/>
</TextBox.InputBindings>
</TextBox>
EngineerCommand:
void RelayCommands()
{
EngineerCommand = new SimpleDelegateCommand(x => EngineerFunction(x))
{
MouseGesture = MouseAction.LeftClick
};
}
void EngineerFunction (object _engineername)
{
EngineerNameClicked = (_engineername as TextBox).Text;
}
public class SimpleDelegateCommand : ICommand
{
public Key GestureKey { get; set; }
public ModifierKeys GestureModifier { get; set; }
public MouseAction MouseGesture { get; set; }
Action<object> _executeDelegate;
public SimpleDelegateCommand(Action<object> executeDelegate)
{
_executeDelegate = executeDelegate;
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
}
If the EngineerCommand command is defined in the same view model class as the collection to which you bind the ItemsSource property of the ItemsControl to (Engineer), you should use a RelativeSource for your binding(s) in the ItemTemplate:
<MouseBinding Command="{Binding DataContext.EngineerCommand, RelativeSource={RelativeSource AncestorType=ItemsControl}}" ... />
I am working with WPF and looking for a best approach for creating re-usable expanders that are customized. Specifically the Expander header would remain the same, the content would vary.
The Expander header would have 6 buttons all wired to the same methods, which will be enforced via an interface.
Assume this is Expander 1
And this is another expander
The actual content will be text, buttons, whatever. It just has text for demo purposes.
The expander is meant to be used within a User Control, in which I have 44 of them, and don't want to repeat the code all over the place.
At the moment I am using the UserControls like the following in the Window XAML
xmlns:customcontrols="clr-namespace:MyNamespace.Controls;assembly=MyAssembly"
And the actual usage:
<customcontrols:FlexExtend ..... />
And inside each User Control I am using expander like this
<Expander Style="{StaticResource ModToolPanelStyle}" Background="#403F3B" Name="toolExpand" Header="{x:Static properties:Resources.AdductionAbduction_Label}" Collapsed="toolExpand_Collapsed" Expanded="toolExpand_Expanded">
....... all the inside body stuff
</expander>
Right now I'm looking at having to replicate the code 44 times, one for each expander in each of the 44 user controls that contain the an expander. Is there a way in WPF to make this a custom control that would have the buttons and everything? I'm think no since it wouldn't be able to be bound there for the on click?
UPDATE:
As suggested I created a DataTemplate in a seperate XAML.
<DataTemplate x:Key="DesignExpanderHeaderTemplate">
<DockPanel>
<TextBlock Name="ModName"
Foreground="White"
Text="Balls">
</TextBlock>
<Button Name="MoveUpButton"
Content="MoveUp"
Width="80"
Height="25">
</Button>
</DockPanel>
</DataTemplate>
However now I am having issues binding the button from the user control:
var button = toolExpand.HeaderTemplate.FindName("MoveUpButton", toolExpand) as Button;
button.Click += delegate (object sender, RoutedEventArgs args)
{
MessageBox.Show("The button has been pressed");
};
The button is always null, so it is not able to find it.
This is how the XAML looks
<Expander Style="{StaticResource ModToolPanelStyle}"
Background="#403F3B"
x:Name="toolExpand"
HeaderTemplate="{StaticResource DesignExpanderHeaderTemplate}"
Collapsed="toolExpand_Collapsed"
Expanded="toolExpand_Expanded">
Following the guidance of user2455627 I was able to get this working. The main key was the following:
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}
My datatemplate looks like the following.
<DataTemplate x:Key="DesignExpanderHeaderTemplate">
<DockPanel>
<TextBlock Foreground="White"
Text="{Binding ModName, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</TextBlock>
<Button Command="{Binding MoveUpCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Content="MoveUp"
Width="80"
Height="25">
</Button>
<Button Command="{Binding MoveDownCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Content="MoveUp"
Width="80"
Height="25">
</Button>
<Button Command="{Binding UndoCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
Content="Undo"
Width="80"
Height="25"></Button>
</DockPanel>
</DataTemplate>
Here is how I am using the data template in the XAML
<Expander Style="{StaticResource ModToolPanelStyle}"
Background="#403F3B"
x:Name="toolExpand"
HeaderTemplate="{StaticResource DesignExpanderHeaderTemplate}"
Collapsed="toolExpand_Collapsed"
Expanded="toolExpand_Expanded">
And here is the relevant code behind:
// Commands
public RelayCommand MoveUpCommand { get; set; }
public RelayCommand MoveDownCommand { get; set; }
public RelayCommand UndoCommand { get; set; }
public RelayCommand RedoCommand { get; set; }
public RelayCommand ClearCommnand { get; set; }
public RelayCommand RemoveCommand { get; set; }
// Properties
private string _modName;
// setup ModPanel
ModName = "Something";
MoveUpCommand = new RelayCommand(MoveUp);
MoveDownCommand = new RelayCommand(MoveDown);
UndoCommand = new RelayCommand(Undo);
RedoCommand = new RelayCommand(Redo);
ClearCommnand = new RelayCommand(Clear);
RemoveCommand = new RelayCommand(Remove);
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string ModName
{
get { return _modName; }
set
{
_modName = value;
OnPropertyChanged();
}
}
public void MoveUp(object obj)
{
throw new NotImplementedException();
}
public void MoveDown(object obj)
{
throw new NotImplementedException();
}
public void Undo(object obj)
{
throw new NotImplementedException();
}
public void Redo(object obj)
{
throw new NotImplementedException();
}
public void Remove(object obj)
{
throw new NotImplementedException();
}
public void Clear(object obj)
{
throw new NotImplementedException();
}
public void PreApply()
{
throw new NotImplementedException();
}
I have a listpicker in which i have used interaction trigger to fire selection changed event.But it is showing Xaml parse exception error.
The code in xaml page is:
<toolkit:ListPicker x:Name="listPickerAccount" ItemsSource="{Binding AccountTypeList,Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<i:InvokeCommandAction Command="{Binding AccountTypeSelectionCommand}"
CommandParameter="{Binding ElementName=listPickerAccount}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</toolkit:ListPicker>
I have set property in viewmodel as
public ICommand AccountTypeSelectionCommand
{
get
{
return new RelayCommand<object>((x) =>
{
string item = (string)(x as ListPicker).SelectedItem;
ListPickerSelection(item);
});
}
}
private void ListPickerSelection(string item)
{
if (AccountType != null)
{
AccountType = item;
return;
}
}
In your InvokeCommandAction update CommandParameter like this : CommandParameter="{Binding ElementName=listPickerAccount, Path=SelectedItem}"
Try this and tell me.
Try this :
//Xaml
<i:Interaction.Triggers>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged" >
<command:EventToCommand Command="{Binding TestCommand}" PassEventArgsToCommand="">
</command:EventToCommand>
</i:EventTrigger>
</i:Interaction.Triggers>
</toolkit:ListPicker>
//VIewModel
public RelayCommand TestCommand{ get; set; }
TestCommand= new RelayCommand( TestMethode);
private void TestMethode( SelectionChangedEventArgs args )
{
//TODO args.AddItems can be help
}
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.