I'm using Wpf and I'm passing a List<Value> to a <ItemsControl> in the xaml. I would like to bind the string in the Value Object to the Command of a Button. This xaml part looks like this:
<Grid Margin="0,0,2,0">
<Grid Margin="10">
<ItemsControl Name="details">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,5">
<Grid.ColumnDefinitions>
....
</Grid.ColumnDefinitions>
...
<Button Grid.Column="2"
Content="{Binding ButtonContent}"
Visibility="{Binding ButtonVisibility}"
Command="{Binding ButtonClickMethod}" />
...
My Value Class looks like this:
public class Value
{
...
public string ButtonClickMethod { get; set; }
}
I'm setting the string link this:
v.ButtonClickMethod = "RelatedActivityId_OnClick";
And the Method is in the same class and looks like this:
private void RelatedActivityId_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("RelatedActivityId_OnClick");
}
Everything besides this is working properly and unses the same Object for the binding.
What am I doing wrong?
The Command property of the Button is of type ICommand so you cannot bind it to a string value.
You need to update your ButtonClickMethod to be of type ICommand or create a new property to bind you Command to.
See this answer for a sample implementation of ICommand.
If you need the button to execute code based on a parameter (string value?) then you can use the CommandParameter property, then use that paramters in your Command handler.
public class Value
{
public Value()
{
ButtonCommand = new RelayCommand((a) => true, CommandMethod);
}
public RelayCommand ButtonCommand {get; set; }
public string ButtonClickMethod { get; set; }
private void CommandMethod(object obj)
{
MessageBox.Show(obj?.ToString());
}
}
and the XAML:
<Button Grid.Column="2"
Content="{Binding ButtonContent}"
Visibility="{Binding ButtonVisibility}"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding ButtonClickMethod}" />
The Button.Command property binds only to objects which implement the ICommand interface.
If you want to invoke a method which its name is ButtonClickMethod, you will have to:
Create a class which implements ICommand interface.
Create a object of that class and bind it to your button (bind it to Button.Command).
Pass the Value.ButtonClickMethod as a CommandParameter to your ICommand object.
Use this to invoke any method you would like to.
Related
I'm trying to get the mouse position related to a wpf control (a Canvas in this case) using MVVM Framework with Prism Library.
I already got a solution but I'm not sure if it's a correct way to use the MVVM framework.
Main window:
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="250"/>
</Grid.ColumnDefinitions>
<Border Grid.Column="0" BorderBrush="Gray" BorderThickness="1">
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding CanvasWidth}" Height="{Binding CanvasHeight}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<prism:InvokeCommandAction Command="{Binding MouseMove}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Loaded">
<prism:InvokeCommandAction Command="{Binding Loaded}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Image Source="{Binding Image}" />
</Canvas>
</Border>
<TextBlock Grid.Column="1" Text="{Binding Text}"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding MouseX, StringFormat='X={0}'}" Grid.Column="1" />
<TextBlock Text="{Binding MouseY, StringFormat='Y={0}'}" Grid.Column="1" />
</StackPanel>
</Grid>
In this XAML code snippet the canvas has 2 Event triggers that I use for converting:
the "MouseMove" event to give the XY pointer position
and the "Loaded" event where the tricky part is. Here I pass the instance obj from Canvas to the controller through this EventTrigger, the in the controller I use this code:
Loaded and MouseMove commands definition:
public DelegateCommand<MouseEventArgs> MouseMove { get; private set; }
public DelegateCommand<RoutedEventArgs> Loaded { get; private set; }
Constructor:
public MainWindowViewModel()
{
MouseMove = new DelegateCommand<MouseEventArgs>(GetMousePosition);
Loaded = new DelegateCommand<RoutedEventArgs>(GetCanvas);
}
Properties definition:
private string _mouseX;
public string MouseX
{
get { return _mouseX; }
set { SetProperty(ref _mouseX, value); }
}
private string _mouseY;
public string MouseY
{
get { return _mouseY; }
set { SetProperty(ref _mouseY, value); }
}
private System.Windows.Controls.Canvas _canvas;
public System.Windows.Controls.Canvas Canvas
{
get { return _canvas; }
set { SetProperty(ref _canvas, value); }
}
Methods called by commands:
private void GetCanvas(RoutedEventArgs obj)
{
Canvas = (System.Windows.Controls.Canvas)obj.Source;
}
private void GetMousePosition(MouseEventArgs eventParam)
{
Point position = eventParam.GetPosition(Canvas);
MouseX = position.X.ToString();
MouseY = position.Y.ToString();
}
Is this way a correct usage? Even this working I feel like passing the Canvas obj to the controller I'm doing something like "code behind".
I'm using a converter to do the GetPosition. That gets passed the source and the event args, so you can get away without the LoadedCommand and you keep the MouseEventArgs out of your view model.
xaml:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseMove">
<i:InvokeCommandAction Command="{Binding MouseMoveCommand}" PassEventArgsToCommand="True" EventArgsConverter="{StaticResource GetPositionConverter}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
view model:
public DelegateCommand<Point?> MouseMoveCommand { get; }
converter:
internal class GetPositionConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter, CultureInfo culture )
{
var mouseEventArgs = (MouseEventArgs)value;
return mouseEventArgs.GetPosition( (IInputElement)mouseEventArgs.Source );
}
public object ConvertBack( object value, Type targetType, object parameter, CultureInfo culture )
{
throw new NotImplementedException();
}
}
The converter should have at least minimal error handling, though, this is just an example :-)
I think you are violating MVVM.. because you are referencing UI-type (i.e. System.Windows.Controls.Canvas) in ViewModel.
I'd suggest an approach to keep the ViewModel clean and get whatever data needed from View..
First, Define an interface in ViewModel's namespace, everything ViewModel wants from View will be defined in this interface..
public interface IUiServices
{
(string mouseX, string mouseY) GetMouseCoordinates();
}
Next, Let your Window (or UserControl) that hosts the <Canvas/> implement this interface
public partial class TheWindow : IUiServices {
// ..
private string _mouseX;
private string _mouseY;
public (string mouseX, string mouseY) GetMouseCoordinates() => (_mouseX, _mouseY);
}
Now, Let the Canvas subscribe to MouseMove event
<Canvas MouseMove="Canvas_OnMouseMove"
And add the handler to set the mouse coords variables
private void MainWindow_OnMouseMove(object sender, MouseEventArgs e)
{
Point position = e.GetPosition(sender as Canvas);
_mouseX = position.X.ToString();
_mouseY = position.Y.ToString();
}
Finally, you can register IUiServices in Prism
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
// ...
containerRegistry.RegisterSingleton<IUiServices, TheView>();
// ...
}
And inject it in ViewModel's constructor
public TheViewModel(.. , IUiServices uiServices){
//..
}
Now, wherever you want to get the coordinates, just call uiServices.GetMouseCoordinates().
Note1: From now on, any service ViewModel wants from View, just define it in IUiServices interface, implement it in View and use it in ViewModel
Note2: you might not use want to pass the service to the ViewModel via Prism, then you could inject it via setter injection
private IUiServices UiServices {set; get;}
public SetUiService(IUiServices s){
UiServices = s;
}
And in TheView, you can do the injection (DataContext as TheViewModel)?.SetUiService(this);
Note3: you can remove all of these from your code: the DelegateCommands in your ViewModel and all the code snippets you've there + <i:Interaction.Triggers/> code-block in your .xaml.
I have UserControl with ItemsControl binded to ObservableCollection. DataTemplate in this ItemsControl is a Grid containing TextBox and Button.
Here is some code (Updated):
<UserControl.Resources>
<entities:SeparatingCard x:Key="IdDataSource"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Cards}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox Text="{Binding Id, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" GotFocus="TextBox_GotFocus" Grid.Row="0" Grid.Column="0"/>
<Button DataContext="{Binding Source={StaticResource IdDataSource}}" Command="{Binding Accept}" Grid.Row="0" Grid.Column="1">Accept</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
In model file:
public ObservableCollection<SeparatingCard> Cards { get; set; }
Card class:
class SeparatingCard : INotifyPropertyChanged
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("Id");
}
}
public ActionCommand Accept { get; }
public SeparatingCard()
{
Accept = new ActionCommand(AcceptCommandExecute);
}
private void AcceptCommandExecute(object obj)
{
MessageBox.Show(Id);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Cards are added in runtime and I dynamically get a new textbox-button pair in my UserControl. Now in each pair I need to do the folowing things:
- Be able to check if the text in textbox is correct and disable/enable apropriate button.
- On button click get the text from apropriate textbox and process it.
I'd like all of this done via MVVM. But I only came to solution that directly have access to UI and implements only the second task:
private void Button_Click(object sender, RoutedEventArgs e)
{
var text = (((sender as Button).Parent as Grid).Children
.Cast<UIElement>()
.First(x => Grid.GetRow(x) == 0 && Grid.GetColumn(x) == 0) as TextBox).Text;
MessageBox.Show(text);
}
Update
As was suggested I tried to move ICommand logic to SeparatingCard class. Now it's always return null and I can't check what object of SeparatingCard class my command refers to. Updates are in the code above.
Instead of using Button.Click, Use Button.Command, which you can bind to some command in SeparatingCard.
Please have a look in this tutorial:
http://www.codeproject.com/Tips/813345/Basic-MVVM-and-ICommand-Usage-Example
Then, SeparatingCard ViewModel will contain an ICommand object which you can bind to Button.Command.
So if the user clicks the button, the event will be directed to the corresponding SeparatingCard object's command.
In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.
I have a class MyDataCollection that contains MyMetaData and MyData. In my application i have two usercontrolls that display input fields to the user. One for the MyMetaData and the other for MyData. Both usercontrols are included in the MainPage.
My Question is: How should i get the data from the usercontrols then the user klicks on the save-button (located on the mainpage)?
Update
I have changed my code accordingly to blindmeis post but now the MetaDataView is not shown:
<UserControl.Resources>
<DataTemplate x:Key="MetaDataTemplate">
<view:MetaDataView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentPresenter Content="{Binding MetaDataTemplate}"/>
</Grid>
why not doing mvvm the easy way?(viewmodel first). you say you have a mainpage - this means you also have a mainpageviewmodel. your mainpageviewmodel handles at least the save command. now you want to show MyMetaData and MyData on your mainpage. so the easy way would be to create one MyMetaData instance and one MyData instance in your mainviewmodel.
public class MainPageViewmodel
{
public ICommand SaveCommand { get; set; }
public MyDataViewmodel MyData { get; set; }
public MyMetaDataViewmodel MyMetaData { get; set; }
public MainPageViewmodel()
{
this.MyData = new MyDataViewmodel();
this.MyMetaData = new MyMetaDataViewmodel();
}
}
public class MyDataViewmodel
{}
public class MyMetaDataViewmodel
{}
your mainpage just need 2 datatemplates and 2 contentpresenter.
//resources
<DataTemplate DataType="{x:Type Local:MyDataViewmodel}">
<view:MyDataUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type Local:MyMetaDataViewmodel}">
<view:MyMetaDataUserControl/>
</DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding MyData}" Grid.Column="0"/>
<ContentPresenter Content="{Binding MyMetaData}" Grid.Column="1"/>
<Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>
because your mainpageviewmodel has both "child" viewmodel, you have all information you want on your savecommand.
if you have another scenario pls update your question, maybe post some code.
EDIT: i have no silverlight so that just a suggestion: maybe rachel can give you a better answer.
<Grid>
<ContentPresenter Content="{Binding MyMetaData}" ContentTemplate="{StaticResource MetaDataTemplate}"/>
</Grid>
if silverlight cant handle datatemplates with datatype you could just put the usercontrol there directly.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<view:MyDataUserControl DataContext="{Binding MyData}" Grid.Column="0"/>
<view:MyMetaDataUserControl DataContext="{Binding MyMetaData}" Grid.Column="1"/>
<Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>
Since you tagged this question as MVVM, your ViewModels should contain both your SaveCommand and all the data needed to perform the actual save
Your MainViewModel should contain MyMetaData and MyData properties (which are bound to their respective UserControls), and each of those objects should contain properties for any data needed in the UserControl. For example, if your UserControl had a TextBox for Name, then your data object should have a property for the Name that the TextBox binds to.
If the Save button is located in one of those UserControls then the respective ViewModel should have a SaveCommand that gets executed when the Button is clicked. All the data needed for the Save is also located in that ViewModel, so you're good to go.
If your MainViewModel is in charge of saving the data, then it should be able to hook into your sub ViewModel's SaveCommand and attach it's own method, such as
this.MyData.SaveCommand = this.SaveCommand();
and all the data needed for the save can be found in this.MyData
If the SaveButton is located in your MainView, and not in one of the UserControls, then the SaveCommand should be part of MainViewModel, and all the data needed for the save can be found in this.MyData or This.MyMetaData.
Remember, with MVVM your ViewModels are your application. The View is just a pretty interface that allows users to interact with your ViewModels.
You should use Two-way bindings to automatically update the value in your controller. Take a look at this article.
Here's an example:
<TextBox Text="{Binding MyMetaData, Mode=TwoWay }" />
<TextBox Text="{Binding MyData, Mode=TwoWay }" />
I'll give you a little sample, how you can use the MVVM Light Messenger for ViewModel-to-ViewModel communication. Say you have an MyDataCollection class:
public class MyDataCollection
{
public int MyData;
public string MyMetaData;
}
On your MainViewModel you have a RelayCommand (from MVVM light toolkit) binded by your View's SaveButton. When the Connad is executed, you will have to send a Message with a callback action to request data from the subcriber. The callback takes MyDataCollection as parameter:
public class MainViewModel : ViewModelBase
{
public RelayCommand SaveCommand { get; private set; }
//Ctor
public MainViewModel()
{
SaveCommand = new RelayCommand(
() =>
Messenger.Default.Send<NotificationMessageAction<MyDataCollection>>(
new NotificationMessageAction<MyDataCollection>("SaveData", SaveCallback)));
}
private void SaveCallback(MyDataCollection dataCollection)
{
// process your dataCollection...
}
}
The UserControlViewModel has properties the InputTextBoxes are binded too. It just has to register to the message and call the callback with data properties:
public class UserControlViewModel : ViewModelBase
{
//Properties
public string UserControlMetaData { get; set; }
public int UserControlData { get; set; }
//Ctor
public UserControlViewModel()
{
Messenger.Default.Register<NotificationMessageAction<MyDataCollection>>(this, MessageReceived);
}
// private Method to handle all kinds of messages.
private void MessageReceived(MessageBase msg)
{
if(msg is NotificationMessageAction<MyDataCollection>)
{
var actionMsg = msg as NotificationMessageAction<MyDataCollection>;
if(actionMsg.Notification == "SaveData") // Is this the Message, we are looking for?
{
// here the MainViewModels callback is called.
actionMsg.Execute(new MyDataCollection() {MyData = UserControlData, MyMetaData = UserControlMetaData});
}
}
}
}
You will have to use messengers or you will have to set the properties over the ViewModelLocator
Messenger example of how I use it to set the UI language
ViewModel A, I register a listener here with the "SetLanguage" token:
Messenger.Default.Register<string>(this, "SetLanguage", false, input =>
{
SetLanguage(input);
});
ViewModel B, here I send the message with the "SetLanguage" token:
Messenger.Default.Send("en-EN", "SetLanguage");
ViewModelLocator example in ViewModel A, I access data in ViewModel B over the locator:
short value = 12;
var myFilteredDataList = ViewModelLocator.ViewModelBStatic.MyDataList.Any(m => m.code == value);
I have two solutions now:
View:
<ContentPresenter Content="{Binding MyMetaDataView}" />
ViewModel:
public MetaDataViewModel MyMetaDataViewModel { get; set; }
public MetaDataView MyMetaDataView { get; set; }
public MainViewModel()
{
MyMetaDataViewModel = new MetaDataViewModel();
MyMetaDataView = new MetaDataView();
MyMetaDataView.DataContext = MyMetaDataViewModel;
}
or ----
View:
<UserControl.Resources>
<DataTemplate x:Key="MetaDataViewTemplate">
<view:MetaDataView />
</DataTemplate>
</UserControl.Resources>
...
<ContentPresenter Content="{Binding MyMetaDataViewModel}" ContentTemplate="{StaticResource MetaDataViewTemplate}"/>
ViewModel:
public MetaDataViewModel MyMetaDataViewModel { get; set; }
public MainViewModel()
{
MyMetaDataViewModel = new MetaDataViewModel();
}
I created an attached property, AttachedBehaviorsManager.Behaviors that is to be used as an MVVM helper class that ties events to commands. The property is of type BehaviorCollection (a wrapper for ObservableCollection). My issue is that the Binding for the Behavior's Command always winds up being null. When used on the buttons it works just fine though.
My question is why am I losing my DataContext on items inside of the collection, and how can I fix it?
<UserControl x:Class="SimpleMVVM.View.MyControlWithButtons"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:behaviors="clr-namespace:SimpleMVVM.Behaviors"
xmlns:con="clr-namespace:SimpleMVVM.Converters"
Height="300" Width="300">
<StackPanel>
<Button Height="20" Command="{Binding Path=SetTextCommand}" CommandParameter="A" Content="Button A" />
<Button Height="20" Command="{Binding Path=SetTextCommand}" CommandParameter="B" Content="Button B"/>
<TextBox x:Name="tb" Text="{Binding Path=LabelText}">
<behaviors:AttachedBehaviorsManager.Behaviors>
<behaviors:BehaviorCollection>
<behaviors:Behavior Command="{Binding Path=SetTextCommand}" CommandParameter="A" EventName="GotFocus"/>
</behaviors:BehaviorCollection>
</behaviors:AttachedBehaviorsManager.Behaviors>
</TextBox>
</StackPanel>
You bind to the command because this is using the MVVM (Model-View-ViewModel) pattern. The datacontext of this user control is a ViewModel object containing a property that exposes the command. Commands do not need to be public static objects.
The buttons in the shown code have no problem executing. They are bound to to the SetTextCommand in the viewmodel:
class MyControlViewModel : ViewModelBase
{
ICommand setTextCommand;
string labelText;
public ICommand SetTextCommand
{
get
{
if (setTextCommand == null)
setTextCommand = new RelayCommand(x => setText((string)x));
return setTextCommand;
}
}
//LabelText Property Code...
void setText(string text)
{
LabelText = "You clicked: " + text;
}
}
The problem is that the binding to the same SetTextCommand that works in the buttons is not recognized in the behavior:Behavior.
Why are you binding to the command? Commands are meant to be setup this way:
<Button Command="ApplicationCommands.Open"/>
Suppose you define a command class like so:
namespace SimpleMVVM.Behaviors {
public static class SimpleMvvmCommands {
public static RoutedUICommand SetTextCommand { get; }
}
}
You would use it like so:
<Button Command="behaviors:SimpleMvvmCommands.SetTextCommand"/>
The MVVM pattern isn't applicable the way you're using it. You'd put the command handler on the VM, but commands themselves are meant to be in the static context. Please refer to the documentation on MSDN for further information.