Xaml pass object with command - c#

Lets say I have class that looks like this:
public class User
{
public string Name { get; set; }
public int Age { get; set; }
}
User is part of the viewModel I use in my view, i try to update its properties like this:
<TextBox Text="{Binding User.Name}"></TextBox>
<TextBox Text="{Binding User.Age, mode=two-way, updatesourcetrigger = onpropertychanged}"></TextBox>
<Button Content="Save user" Command="{Binding SaveUserCommand}" CommandParameter="{Binding User}" />
Here is the viewmodel:
public RelayCommand<User> SaveUserCommand { get; private set; }
public MainViewModel()
{
SaveUserCommand = new RelayCommand<User>(SaveUser);
}
public void SaveUser(User user)
{
//logic for saving user
}
I thought this would let me change the values in the textboxes and then pass user to the viewmodel. The problem is that nothing gets sent, SaveUser() gets triggred with null.
Can someone see what I am missing/missunderstanding?
Thank you!
EDit:
This is the property that represents my user in the view:
private User _user;
public User User
{
get
{
return _user;
}
set
{
if (_user != value)
{
_user = value;
RaisePropertyChanged("User");
}
}
}

Your Bindings on your TextBox controls need to be Mode=TwoWay. TextBox controls also only update the source value on LostFocus so you may want to change that with UpdateSourceTrigger=PropertyChanged on the Binding as well.
Your ViewModel also seems to be lacking a public property for User.
You need:
public User User {get;set;}
...according to your current Binding definitions.
I also can't see the definition of the ViewModel class, but it should implement INotifyPropertyChanged (or a base class should implement it).
Edit:
OP hadn't instantiated the User property.

Related

Command not firing on current item

I have a CarouselView. The ItemsSource is binded to an ObservableCollection of type AlarmEvent. AlarmEvent is a base class for different types of alarm events (Generic, Fr, Vfr, Anpr). The ItemTemplate changes depending on the type of CurrentItem. This works.
Xaml:
<CarouselView x:Name="cvAlarmEvents"
ItemsSource="{Binding AlarmEvents}"
CurrentItem="{Binding SelectedAlarmEvent}"
CurrentItemChangedCommand="{Binding CmdSelectedAlarmEventChanged}"
ItemTemplate="{StaticResource AlarmEventDataTemplateSelector}"
Grid.Row="2"
Margin="0, 2, 0, 0"
BackgroundColor="#141d3d"
IsVisible="true"/>
Template Selector:
public class AlarmEventDataTemplateSelector : DataTemplateSelector
{
public DataTemplate Generic { get; set; }
public DataTemplate Fr { get; set; }
public DataTemplate Tfr { get; set; }
public DataTemplate Anpr { get; set; }
public AlarmEventDataTemplateSelector()
{
Generic = new DataTemplate(typeof(GenericView));
Fr = new DataTemplate(typeof(FrView));
Tfr = new DataTemplate(typeof(TfrView));
Anpr = new DataTemplate(typeof(AnprView));
}
protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
{
var alarmEvent = item as AlarmEvent;
switch (alarmEvent.Type)
{
case Enums.AlarmEventType.None:
return null;
case Enums.AlarmEventType.Generic:
return Generic;
case Enums.AlarmEventType.FaceRecognition:
return Fr;
case Enums.AlarmEventType.TemperatureFaceRecognition:
return Tfr;
case Enums.AlarmEventType.ANPR:
return Anpr;
default:
return null;
}
}
}
GenericView, FrView etc inherit from ContentView. The actual command bindings are inside the DataTemplate (ContentView) which is returned from AlarmEventDataTemplateSelector:
<ImageButton Source="CamWhite1"
BackgroundColor="Transparent"
Scale="1"
BorderColor="White"
BorderWidth="3"
Padding="10"
Margin="10, 5"
Command="{Binding CmdAddPic}" />
I have plenty of data bindings on the model which all work, aside from the Command bindings. I've tested the buttons with OnClick events and they fire as they should, which makes me think that the Command data binding is not working.
Here is how the command code looks inside AlarmEvent
public class AlarmEvent : BaseModel
{
public Command CmdAddPic { get; set; }
public AlarmEvent()
{
CmdAddPic = new Command(AddPic);
}
public void AddPic()
{
// I dont fire and this makes me sad ;(
}
}
I cannot find a single thread online about this problem. I can find countless of threads where they are trying to bind a command from a selected item to their viewmodel. THIS IS NOT WHAT I WANT. I simply want the command binding to work on my data model (AlarmEvent), and I am stumped as to why it doesn't work. Perhaps I am missing something obvious?
I am a silly bum for not realising this sooner, the fix was:
Instead of auto property like so -
public Command CmdSubmitUserMedia{ get; set; }
I needed to use a full property like so -
private Command cmdSubmitUserMedia;
[Ignore]
public Command CmdSubmitUserMedia
{
get { return cmdSubmitUserMedia; }
set { SetProperty(ref cmdSubmitUserMedia, value); }
}

WPF, Binding not working even with property changed implemented

I'm running into a problem binding to a 'StatusBarItem'. I'll add that I'm using 'PropertyChanged.Fody' to implement my models where I want changes to be raised.
Heres the 'XAML' snippet:
<StatusBar Grid.Row="2"
Background="Transparent"
Padding="5,0">
<StatusBarItem Content="{Binding Path=Application.Status, Source={x:Static m:Locator.Instance}}"/>
</StatusBar>
Base Model:
[AddINotifyPropertyChangedInterface]
public class IModel {
}
Application Model:
public sealed class ApplicationModel : IModel {
static ApplicationModel() {
Instance = new ApplicationModel() {
Status = "Ready"
};
}
public static ApplicationModel Instance {
get;
}
public string Status {
get;
set;
}
}
Locater Model:
public sealed class Locator : IModel {
static Locator() {
Instance = new Locator();
}
public static Locator Instance {
get;
}
public ApplicationModel Application => ApplicationModel.Instance;
}
When I want to change 'Status' from anywhere in code, I do so like:
Locator.Instance.Application.Status = message;
Now my problem isn't that 'Status' wont change, it changes just fine. When the application runs, it updates the XAML once with the default value set in ApplicationModel. It's the XAML not updating to the new changes once I set a new value. I feel I've done everything right as I'm using my 'BaseModel' in other places without problems.
FIXED:
I removed the 'PropertyChanged.Fody' package along with 'FodyWeavers.xml' then re-added them after deleting both '\bin' and '\obj' and finally a solution clean.

Make binding work on property in a different class

So I have a class MainViewModel in which I have a button. The button navigates to a view which has its own view model, let's call in ListViewModel. It resides inside MainViewModel. It has an ObservableCollection called WorkOrders.
In my main view model, I have a property, which returns the number of items, in the list in my ListViewModel. However, if I bind my button text to this property (NumberOfWorkOrders), then nothing happens, when WorkOrders.Count() changes. Even if I call OnPropertyChanged("NumberOfWorkOrders").
However, it does work, if I bind to the identical property inside the ListViewModel. How come it does not work, with the property in the MainViewModel? Is it because the notification from INotifyPropertyChanged does not work in a different view model?
Button binding which DOES NOT work (uses property from MainViewModel)
<Button
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
Button binding which DOES work (uses property from ListViewModel)
<Button
DataContext="{Binding LVM}"
Content="{Binding NumberOfWorkOrders}"
ContentStringFormat="WorkOrders ({0})" />
MainViewModel.cs
public class MainViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
private ListViewModel listViewModel = new ListViewModel();
// This is where I would like to bind my button text to
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
// I would prefer not to have this
public ListViewModel LVM
{
get { return listViewModel; }
}
}
ListViewModel.cs
public class ListViewModel : BindableBase
{
// INotifyPropertiesChanged is implemented in BindableBase
public ObservableCollection<WorkOrder> WorkOrders
{
get; set;
}
// I would prefer to use the version of this in the MainViewModel
public int NumberOfWorkOrders
{
get { return listViewModel.WorkOrders.Count(); }
}
public void RefreshWorkOrders()
{
(...) // Load work orders and add them to collection
OnPropertyChanged("NumberOfWorkOrders");
}
}
You are running into this problem
, where you have aggregated property which required additional job: you will have to subscribe to ListViewModel.PropertyChanged and rise notification for MainViewModel.NumberOfWorkOrders property:
public class MainViewModel : BindableBase
{
readonly ListViewModel listViewModel = new ListViewModel();
public int NumberOfWorkOrders => listViewModel.WorkOrders.Count();
public MainViewModel()
{
// since you already rise notification in ListViewModel
// listen to it and "propagate"
listViewModel.PropertyChanged += (s, e) =>
{
if(e.PropertyName == nameof(ListViewModel.NumberOfWorkOrders))
OnPropertyChanged(nameof(NumberOfWorkOrders));
};
}
}
First button (which does NOT work) has MainViewModel as data context, binding will only listen for notification in this class.
As a simple fix you can include LVM in Path of binding. Binding is smart and will start listening to both events: of main view model and of that instance given by LVM property:
<Button Content="{Binding LVM.NumberOfWorkOrders}" ... />
you can delete NumberOfWorkOrders from MainViewModel then.
The reason your MainViewModel binding doesn't work, because you call OnPropertyChanged("NumberOfWorkOrders") from ListViewModel context.
I would suggest in MainViewModel to sign to changes in listViewModel.WorkOrders, and fire OnPropertyChanged("NumberOfWorkOrders") from MainViewModel, when WorkOrders changed. You need to look into documentation of ObservableCollection to find how to sign for collection changed notifications.

Hierarchical viewmodels which represent UI structure. What is better approach?

Sorry for long and descriptive question, but it really bugs me for a long time. I have a problem with MVVM pattern.
I wrote application which actually works, but I don't think it's in a good style. My view model strucure looks like tree: it has references to all child viewmodels which are used to render proper view in ContentPresenter.
Take a look at sample GUI:
Home tab
--------------------------------------
HOME SETTINGS ADMINPANEL
======---------------------------------
______________________________________
/////////
///////// Home content
/////////
Settings tab
--------------------------------------
HOME SETTINGS ADMINPANEL
------============---------------------
______________________________________
settings1 > settings2 > other...
/////////
///////// Settings1 content
/////////
Notice the submenu which appears only in Settings view. Every switchable view is somehow dependent on model type. settings1 is visible only if model property is Type.One, and settings2 is visible when property is Type.Two.
Take a fast look into my current code: common interface for every view that I can change by click. PageHeader is displayed on button change content view:
public interface IPageVM
{
string PageHeader { get; set; }
}
and view models:
public class WindowVM : ViewModelBase
{
public ObservableCollection<IPageVM> ViewModels { get; set; }
public IPageVM CurrentTab { get; set; }
public ICommand ChangeViewModel { get; set; }
private Model _model;
public WindowVM()
{
_model = new Model();
ViewModels = new ObservableCollection<IPageVM>();
ViewModels.Add(new HomeVM(model));
ViewModels.Add(new SettingsVM(model));
if(_model.Admin)
ViewModels.Add(new AdminVM(model));
}
}
public class HomeVM : ViewModelBase, IPageVM
{
public string PageHeader { get { return "HOME"; } }
string Property { get; set; }
public HomeVM(Model model)
{
this.Property = model.Property;
}
}
public class SettingsVM : ViewModelBase, IPageVM
{
public string PageHeader { get { return "SETTINGS"; } }
public ObservableCollection<IPageVM> Tabs { get; set; }
public IPageVM CurrentTab { get; set; }
public ICommand ChangeViewModel { get; set; }
public SettingsVM(Model model)
{
Tabs = new ObservableCollection<IPageVM>();
if(model.Type = Type.One)
Tabs.Add(new Settings1VM(model));
if(model.Type = Type.Two)
Tabs.Add(new Settings2VM(model));
Tabs.Add(new OtherSettingsVM());
CurrentTab = Tabs[0];
}
}
public class Settings1VM: ViewModelBase, IPageVM
{
public string PageHeader { get { return "settings1"; } }
public Settings1VM(Model model)
{
}
}
public class Settings2VM: ViewModelBase, IPageVM
{
public string PageHeader { get { return "settings2"; } }
public Settings1VM(Model model)
{
}
}
xaml:
display buttons in ItemsControl that will change CurrentViewModel and render appropriate view bounded to viewmodels' type by DataTemplate.
Pros:
it already works
I can easily tell how does my GUI structure looks, because root viewmodel contains its children viewmodels and so on.
it's easy to inject data model to children by constructor.
Cons:
xaml code is all a DataTemplate
UserControls are empty in preview mode, it's hard to edit GUI
terrifying future: how will look my viewmodels structure if my application will grow a bit?
So I decided to change my ViewModels to something that looks more like WPF MVVM:
public class WindowVM : ViewModelBase
{
public bool AdminMode { get; set; }
public WindowVM()
{
_model = new Model();
AdminMode = _model.Admin;
AnotherTabVisibilityDependency = _model.Dependency;
}
}
xaml:
<TabControl>
<TabItem Header="HOME">
<TabItem.DataContext>
<vm:HomeVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="SETTINGS">
<TabItem.DataContext>
<vm:SettingsVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="ADMINPANEL" Visibility="{Binding AdminMode, Converter BoolToVisibility}">
<TabItem.DataContext>
<vm:AdminVM/>
</TabItem.DataContext>
</TabItem>
<TabItem Header="DEPENDANT" Visibility="{Binding AnotherTabVisibilityDependency, Converter BoolToVisibility}">
</TabItem>
</TabControl>
As you probably see, everything is clean, chaning UI is easy; not too much templates to write. Everything looks fine... But there is a thing I don't understand: Now each ViewModel is a separate instance that doesn't know about its parent. I can't pass model that easy.
Questions:
how can I inject data model into each viewmodel? I can't do it via xaml. Do I need to have a global static class with program state, or IoC? Maybe another way?
any alternative approaches? Maybe my second approach also isn't good?
does it make sense to rewrite my logic which actually works? I really hope it does (I hate my viewmodels' code).

Databinding with unnamed textboxes and listbox

I have been taught lately when using WPF and databinding it is good practice to not name any of the fields but only to associate them with the properties in the other classes. My problem right now is how do I add the data from 3 textboxes (the user enters), save the binded information to the model which then posts the account information into the listbox on the side. I need to add the data to my model. My code from main.xaml is below:
<ListBox ItemsSource="{Binding Path=Files}" SelectedItem="{BindingPath=CurrentItem}" />
<TextBox Text="{Binding Path=bankaccount}"/>
<TextBox Text="{Binding Path=accountnumber}"/>
<TextBox Text="{Binding Path=accounttype}"/>
<Button Content="Save Data To Listbox" Click="Save_Click"/>
Now I will show my FileModel class which holds all of my properties which will be from the textboxes
private short _BankAccount;
private long _AccountNumber;
private char _AccountType;
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
public long accountnumber{ get { return _AccountNumber;} set {_AccountNumber= value; Notify("accountnumber"); } }
public char accounttype{ get { return _AccountType;} set{_AccountType= value; Notify("accounttype"); } }
I use a class called ProgramModel As my middle point between the Mainpage and my FileModel page and here is that code:
public class ProgramModel : INotifyPropertyChanged
{
public ObservableCollection<FileModel> Files { get; set; }
private FileModel _currentItem;
public FileModel CurrentItem { get { return _currentItem; } set { _currentItem = value; Notify("CurrentItem"); } }
public ProgramModel()
{
Files = new ObservableCollection<FileModel>();
}
And to finish it off I have my mainpage:
internal partial class MainWindow
{
public ProgramModel Model { get; set; }
private ViewSettings _viewSettings = new ViewSettings();
public MainWindow()
{
InitializeComponent();
this.DataContext = Model = new ProgramModel();
}
private void Save_Click(object sender, RoutedEventArgs e)
{
FileModel filemodel = new FileModel();
Model.Files.Add(new FileModel( filemodel.bankaccount, filemodel.accountnumber, filemodel.accounttype));
}
I feel like I am adding to the Files Collection incorrectly from the save button event. If you guys can help me out that would be great! All 3 textboxes and the listbox are on the Main page. Let me know if you have any questions. Also, this is a learning experience so let me know if I posted too much code or not enough. Thanks!
You read the values from a new FileModel instance instead of from what is bound to the view. Code should be this:
Model.Files.Add(new FileModel
(
Model.CurrentItem.bankaccount,
Model.CurrentItem.accountnumber,
Model.CurrentItem.accounttype
));
Make sure CurrentItem is actually initialized with an instance, don't see that in your code. Also, you could use a command here and have all the relevant logic in your bound view model without the need for the event.
Also, right now you bind the current item to the selected item in the ListBox, this will modify an existing instance instead. Not sure if this is intended. If you want those fields to be for input of new instances don't bind the ListBox to it.
I'm not going to answer your question directly because implementing proper data binding will take a bit of code to do so.
Using proper data binding, it is possible to have almost no code behind on your view.cs! (Specially if you start using frameworks)
Please take a look on A Simple MVVM Example for you to follow good practice.
By following this example, you will see that you can also use data binding on buttons and other controls.
Your View Model which is ProgramModel : INotifyPropertyChanged should handle all the work (data processing).
Your model should not handle the UI update notifications thus,
public short bankaccount{ get { return _BankAccount;} set {_BankAccount= value; Notify("bankaccount"); } }
will be moved to the ProgramModel (View Model).
Save_Click method will also be converted into an ICommand and be binded to the button in view like <Button Content="Save Data To Listbox" Command="{Binding SaveExec}"/>
The point is, if you are studying data binding, you should implement it right. Hope you understand...
In the end, it is possible for your Main.cs to only be..
internal partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProgramModel();
}
}
Just a small change and it should work . Change Your bindings as shown below for the TextBoxes.
<TextBox Text="{Binding Path=CurrentItem.bankaccount}"/>
<TextBox Text="{Binding Path=CurrentItem.accountnumber}"/>
<TextBox Text="{Binding Path=CurrentItem.accounttype}"/>

Categories