C# MVVM: Edit a SelectedItem of an ObservableCollection with a RelayCommand - c#

I am quiet new to programming and am currently learning C# and the MVVMC pattern (which is I think basically the same as MVVM pattern).
I need to code a database tool for ChiliPlants for university. There you should be able to edit an existing item from an ObservableCollection.
This ObservableCollection I displayed in a DataGrid, see this:DataGrid
Below the DataGrid there are three buttons: Add, Edit and Delete.
I was able to programm the AddButton, aswell as the DeleteButton.
Unfortunately I don't know how to programm the EditButton.
It should open a new window, where the SelectedItem should be opened like this:EditWindow
Until now my EditButton does the same thing as my AddButton.
See my code here:
View:
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Content="Add" Margin="5,5,0,5" Width="100" Command="{Binding AddCommand}" />
<Button Content="Edit" Margin="5,5,0,5" Width="100" Command="{Binding EditCommand}" />
<Button Content="Delete" Margin="5,5,540,5" Width="100" Command="{Binding DeleteCommand}" />
<Button Content="Sichern" Margin="5,5,5,5" Width="100" Command="{Binding SaveCommand}" />
</StackPanel>
ViewModel:
private ICommand _editCommand;
public ICommand EditCommand
{
get { return _editCommand; }
set { _editCommand = value; }
}
Controller:
public void SDInitialize()
{
var view = new WindowStammdatenverwaltung();
mViewModel = new WindowStammdatenverwaltungViewModel
{
EditCommand = new RelayCommand(EditCommandExecute, EditCommandCanExecute)
};
view.DataContext = mViewModel;
view.ShowDialog();
}
private void EditCommandExecute(object obj)
{
var editedObject = new WindowEditController().EditChiliModel();
if (editedObject != null)
{
mViewModel.Stock.Add(mViewModel.SelectedChili);
}
}
private bool EditCommandCanExecute(object obj)
{
return mViewModel.SelectedChili != null;
}
The problem is with the EditCommandExecute. Currently I have just put the Code for an AddCommandExecute in there. I unfortunately don't know how to code such an EditCommandExecute.
My WindowEditController looks like this:
public class WindowEditController
{
WindowEdit mView;
public ChiliModel EditChiliModel()
{
mView = new WindowEdit();
WindowEditViewModel mViewModel = new WindowEditViewModel
{
ChiliModel = new ChiliModel(),
OkCommand = new RelayCommand(ExecuteOkCommand),
CancelCommand = new RelayCommand(ExecuteCancelCommand),
};
mView.DataContext = mViewModel;
if (mView.ShowDialog() == true)
{
return mViewModel.ChiliModel;
}
else
{
return null;
}
}
private void ExecuteOkCommand(object obj)
{
mView.DialogResult = true;
mView.Close();
}
private void ExecuteCancelCommand(object obj)
{
mView.DialogResult = false;
mView.Close();
}
I know, that I could let the user edit the SelectedItem inside the DataGrid, but this is not allowed in my task...
Could I maybe use the same window as for my AddCommand? Basically they should look the same, the EditWindow should just already contain the information of the SelectedItem.
I looked up almost every entry similar to this topic, but I did not find a simple solution. Or a solution which I was able to understand with my bad coding skills :( ...
I would be very happy if you guys could help me. Please keep it simple for this newbie :)
What I already tried:
I tried to add a CommandParameter to my Button looking like this: CommandParameter="{Binding SelectedItem, ElementName=StockDataGrid}"
But this still didn't open the window containing the data of the SelectedItem. It just opened a completely new Window for a new Item.

Use CommandParameter just after the Command property, and bind it to the SelectedItem of the DataGrid.
For example, suppose that you DataGrid has the attribute Name=MyDataGrid.
The Button becomes:
<Button Content="Edit"
Margin="5,5,0,5"
Width="100"
Command="{Binding EditCommand}"
CommandParameter="{Binding SelectedItem, ElementName=MyDataGrid}"/>
When EditCommandExecute(object obj) is executed, obj is actually the current SelectedItem that you want.

Related

How to return from target view model to source view model with data collected in target view model and invoke method in source viewmodel

I have this view (fragment) with 2 buttons. If I click the left button, View 2 opens. If I click the right button, View 3 opens. I am using Caliburn Micro. Hence, the Button's x:Name value is the name of the View Model's method which is invoked after clicking the button.
View1:
<StackPanel Name="PnlButtons"
Grid.Row="1"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Orientation="Horizontal"
Opacity="1">
<Button x:Name="ArtikelAuswahl"
Background="Bisque"
Content="Artikel auswählen"
Width="170" Height="25"
FontFamily="Verdana">
</Button>
<Button x:Name="SonderAuswahl"
Background="BlanchedAlmond"
Content="Sonderartikel hinzufügen"
Width="170" Height="25"
FontFamily="Verdana">
</Button>
</StackPanel>
Here are the 2 methods that get invoked after button click. You can see they have the same name.
Now people say that it is prohibited to open a view inside a view model. This is why I am using an IWindowManager instance winmanager inside my methods when I want to open a new view. Instead of creating a new view instance, I create a new viewmodel instance! First question: Is this against the rules of MVVM?
ViewModel1:
public class CreateLieferscheinViewModel : Conductor<object>
{
private IWindowManager winmanager = new WindowManager();
public InventurartikelViewModel inventur = new InventurartikelViewModel();
public SonderartikelViewModel sonder = new SonderartikelViewModel();
public void ArtikelAuswahl()
{
wwinmanager.ShowWindow(inventur, null, null);
}
public void SonderAuswahl()
{
winmanager.ShowWindow(sonder,null,null);
}
/* ToBeImplemented: Invoke this method once `Artikelliste` is filled!!! */
public void ArtikellisteUmformen()
{
for (int k = 0; k < inventur.Artikelliste.Count; k++)
{
Artikelsammlung.Add(new ArtikelModel()); //every selected article will get added to Artikelsammlung
//get each selected article unfiltered (unformatted)
Artikelsammlung[k].Bezeichnung = inventur.Artikelliste[k].ToString();
//Extract the unit out of the Artikel-String
Artikelsammlung[k].Einheit = Zeichenketten.TextFindenVonBisEnde(Artikelsammlung[k].Bezeichnung, "<", ">");
//remove "in <Einheit>" from the Artikel-String
Artikelsammlung[k].Bezeichnung = Zeichenketten.EinheitEntfernen(Artikelsammlung[k].Bezeichnung);
/*
* Bezeichnung and Einheit are now properly formatted...
*/
}
}
}
private ObservableCollection<ArtikelModel> _artikelsammlung;
public ObservableCollection<ArtikelModel> Artikelsammlung
{
get { return _artikelsammlung; }
set
{
_artikelsammlung = value;
OnPropertyChanged("Artikelsammlung");
}
}
Ok, now let's say the ArtikelAuswahl gets invoked. Thanks to Caliburn Micro, View2 shows:
View2:
<Window x:Class="Lieferscheine.Views.InventurartikelView"
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:local="clr-namespace:Lieferscheine.Views"
xmlns:main="clr-namespace:Lieferscheine"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d" Title="Inventurartikel suchen"
Height="450" Width="370">
<StackPanel Height="423" VerticalAlignment="Bottom">
<Label Name="lblArtikelbezeichnung" Content="Artikelbezeichnung:" Margin="20, 20, 20, 0"></Label>
<TextBox Name="BezText"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding Path=BezEnabled}"
cal:Message.Attach="[Event KeyUp] = [Action KeyUpBez($executionContext)]">
</TextBox>
<Label Name="lblLieferant" Content="Lieferant:" Margin="20, 0, 20, 0"></Label>
<TextBox Name="LiefText"
Width="Auto"
Margin="20, 0, 20, 0"
IsEnabled="{Binding Path=LiefEnabled}"
cal:Message.Attach="[Event KeyUp] = [Action KeyUpLief($executionContext)]">
</TextBox>
<Button Name="SucheArtikel"
Content="Suchen"
Width="100" Height="25"
Margin="20, 10,240, 10">
</Button>
<Button x:Name="GesamteListeAnzeigen"
Content="Gesamte Liste anzeigen"
Width="150" Height="26"
Margin="0, -50, 20, 0"
HorizontalAlignment="Right"/>
<main:MultipleSelectionListBox
x:Name="LboxAddArtikel"
SelectionMode="Multiple"
Width="320" Height="220"
Margin="20, 10, 20, 10"
BindableSelectedItems="{Binding Path=MyCollectionOfSelectedIDs}">
</main:MultipleSelectionListBox>
<Button x:Name="FuegeArtikelHinzu"
Content="Hinzufügen"
Width="100" Height="25">
</Button>
</StackPanel>
View2 is data bound to ViewModel2. But before I show you ViewModel2 I want to show you what I can do in View2:
I select 3 articles from a listbox and click on the button on the bottom of view2 to add these articles to a list:
<Button x:Name="FuegeArtikelHinzu"
Content="Hinzufügen"
Width="100" Height="25">
</Button>
The articles are added to a list in the method FuegeArtikelHinzu in viewmodel2:
ViewModel2:
public class InventurartikelViewModel : Screen
{
private List<string> _artikelliste = new List<string>();
public List<string> Artikelliste
{
get { return _artikelliste; }
set
{
_artikelliste = value;
OnPropertyChanged("Artikelliste");
}
}
public bool ArtikellisteUpdated()
{
Filled = Artikelliste != null ? true : false;
return Filled;
}
private bool _filled;
public bool Filled
{
get
{
return _filled;
}
set
{
_filled = value;
OnPropertyChanged("Filled");
if(_filled == true)
{
//TO BE IMPLEMENTED
//then invoke ViewModelA's method `ArtikellisteUmformen()`
}
_filled = false; //I believe I would need to set _filled back
//to false to prevent overflow. Otherwise ViewModelA's method
//`ArtikellisteUmformen()` would get invoked over and over again because
//_filled is always true from now on. Is that correct?
}
}
public void FuegeArtikelHinzu()
{
try
{
//This adds only the multiple selected items to a list
var multi = MyCollectionOfSelectedIDs;
int i = 0;
foreach (string item in multi)
{
Artikelliste.Insert(i, item);
i++;
}
MessageBox.Show("Artikel hinzugefügt!"); //ok, all added...
//call `ArtikellisteUpdated()` and set Property `Filled` (=`Artikelliste` is filled) to true
ArtikellisteUpdated(); //true unless null
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Zuerst Artikel auswählen!"); //you must select an article first...
}
}
}
And here is my problem! I need the selected articles in the list artikelliste IN MY VIEW MODEL1!!!
But I ran into a dead end. I heard that this is solved by implementing IMessenger service but I don't understand how it works in my example. What do I need to do to pass the artikelliste to ViewModel1 according to my example? If you are not familiar with Caliburn Micro, post another solution, either from scratch or with a framework, I don't mind. Thanks in advance for any help!
EDIT:
I can now access ViewModel2 and its Artikelliste property within ViewModel1. However, I would like to invoke ViewModel1's method ArtikellisteUmformen() as soon as ViewModel2's Artikelliste got updated. How do I do that?
This is what I would like to do:
When the Artikellistehas been filled, you can invoke an event on ViewModel2, for example ArtikelListeUpdated. ViewModel1 listens to that event and reacts to it if necessary. You even don't need the event in case you don't have to react to it immediately.
Creating a ViewModel instance and using the WindowManager interface is not against MVVM. You should be fine with that.
I am not familiar with the Caliburn Micro Framework. But what about this:
Hold the artikelliste as a property in ViewModel2.
Hold an instance of ViewModel2 as a property in ViewModel1. You pass that instance when invoking the WindowManager.ShowWindow method.
Then, when the artikelliste has been filled, you can invoke an event on ViewModel2, for example ArtikelListeUpdated. ViewModel1 listens to that event and reacts to it if necessary. You even don't need the event in case you don't have to react to it immediately.
Edit: Example for the last step:
public class ViewModel1 {
public ViewModel2 ChildVm {get;} = new ViewModel2();
public ViewModel1() {
ChildVm.Updated += OnChildUpdated;
}
private void OnChildUpdated(object pSender, EventArgs pArgs) {
// do what is needed
}
}
public class ViewModel2 {
public event EventHandler Updated;
public void DoStuff()
{
// do something
if (Updated != null)
Updated.Invoke(this, EventArgs.Empty);
}
}
Be aware that ViewModel2 has an internal reference to ViewModel1 in this case, therefore preventing ViewModel1 from being garbage-collected.
I recommend you look at the basics of EventHandling for C#, before proceeding with your project: Understanding events and event handlers in C#

Get the commandparameter value when clicking a button MVVM

Other examples are not working or are completly different from what I do.
So I have a button on my XAML page like this:
<Button Width="100"
Height="50"
Margin="0 0 10 0"
Command="{Binding MenuButtonViewModel.MenuButtonCommand, Source={StaticResource Locator}}"
CommandParameter="{Binding Parameter}"
IsEnabled="{Binding IsEnabled}">
And on my ViewModel this:
public RelayCommand MenuButtonCommand
{
get
{
return new RelayCommand(() =>
{
});
}
}
The question is how do I get the value of the commandparameter on my ViewModel?
public RelayCommand<String> MenuButtonCommand
{
get
{
return new RelayCommand((parameter) =>
{
Text = parameter;
});
}
}
This is not working, have no idea how to do this without having to use codebehind to pass the commandparameter value to the ViewModel.
Ok found the solution:
public RelayCommand<string> Command
{
get
{
return new RelayCommand<string>(parameter =>
{
var str = parameter;
});
}
}
Thanks guys.

WPF Button Command not firing, what am I missing?

I feel bad posting this because I see a ton of similar posts, but after going through them all I can't quite diagnose my issue still. What I have is a WPF app designed with the MVVM pattern and using a RelayCommand() implementation for commands.
In my user control's XAML I set the data context here :
<UserControl.DataContext>
<viewModel:SidePanelViewModel />
</UserControl.DataContext>
Then further down in the XAML I have this snippet where I assign a button's command
<TextBlock FontWeight="Bold" Margin="0,0,0,10">Service List</TextBlock>
<ListBox MaxHeight="100"
ItemsSource="{Binding ServiceList}"
SelectedItem="{Binding ServiceToRemove}">
</ListBox>
<Button HorizontalAlignment="Left" Width="60" Margin="0,10"
Command="{Binding RemoveServiceCommand}">Remove</Button>
I am binding the button to the Command RemoveApplicationCommand which I define in the SidePanelViewModel here :
public ICommand RemoveServiceCommand
{
get { return new RelayCommand(RemoveService, CanRemoveService); }
}
private void RemoveService()
{
ServerList.Remove(ServiceToRemove);
}
private bool CanRemoveService()
{
return true;
}
The problem
If I debug, the getter for RemoveServiceCommand will be reached when the button starts up, but when I click the button the code doesn't reach it. I had a very similar implementation (or so I think) working before, so this is really puzzling me. How can I get the command to fire on click?
Command="{Binding RemoveApplicationCommand}"
Did you mean RemoveServiceCommand?
Turns out the debugger was going over RemoveService the entire time but I had not put a breakpoint there. I had a wrong name in my RemoveService implementation ServerList.Remove() should have been ServiceList.Remove(). I assumed the debugger would hit a breakpoint in the RemoveServiceCommand property's getter but it turns out it doesn't hit that when you click the button.
You're returning a new RelayCommand in your getting, but not saving / caching the instance. Save it in a member variable.
if (_cmd == null)
_cmd = new ....
return _cmd;
Try implementing like this
private ICommand finishCommand;
public ICommand FinishCommand
{
get
{
if (this.finishCommand == null)
{
this.finishCommand = new RelayCommand<object>(this.ExecuteFinishCommand, this.CanExecutFinishCommand);
}
return this.finishCommand;
}
}
private void ExecuteFinishCommand(object obj)
{
}
private bool CanExecutFinishCommand(object obj)
{
return true;
}

How to Update LisBox ItemSource On Button Click

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 :)

How do I pass the Button as CommandParameter from XAML in a Xamarin.Forms Page?

I would like to pass a Xamarin.Forms.Button in it's own Command as the CommandParameter to my ViewModel. I know how to achieve this from the code behind e.g. ...
XAML (with most properties missed out for brevity)
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"/>
XAML.cs
public partial class MyTestPage
{
public MyTestPage()
{
InitializeComponent();
myButton.CommandParameter = myButton;
}
}
ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
ButtonClickCommand = new Command(
(parameter) =>
{
var view = parameter as Xamarin.Forms.Button;
if (view != null)
{
// Do Stuff
}
});
}
public ICommand ButtonClickCommand { get; private set; }
}
... BUT is it possible to declare the CommandParameter in the XAML itself? Or in other words what is the binding syntax to set the parameter to the button itself?
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{[WHAT WOULD GO HERE]}"/>
btw I've already tried CommandParameter="{Binding RelativeSource={RelativeSource Self}}" and that didn't work.
Thanks,
Xamarin.Forms has a Reference markup extension that does just that:
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
Although, this is the first time I'm seeing this need, and you probably can better separate your Views from your ViewModels and solve this by using a cleaner pattern, or by not sharing a command across buttons.
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter="{x:Reference myButton}"/>
In your ViewModel
public YourViewModel()
{
ButtonClickCommand= new Command(ButtonClicked);
}
private async void ButtonClicked(object sender)
{
var view = sender as Xamarin.Forms.Button;
}
A simple method would be:
In XAML:
<Button Text="BUTTON-TEST"
Clicked="Avaliar"
CommandParameter="like"/>
In C#:
private void Avaliar(object sender, EventArgs e)
{
Console.WriteLine(((Button)sender).CommandParameter);
}
<Button x:Name="myButton"
Text="My Button"
Command="{Binding ButtonClickCommand}"
CommandParameter={Binding RelativeSource=
{RelativeSource
Mode=FindAncestor,
AncestorType={x:Type Button}}/>
Should work, but im still at a loss why you need the button? The point of MVVM is to seperate Data and UI. everything you should need todo to the button can be done via DataBindings.
If the above doesnt work, the only other thing to try is to give the button an x:Key and CommandParamter = {StaticResource 'x:Key'}

Categories

Resources