How to pass the ViewModel instance into a user defined Command - c#

Tips: Chinese content is just a translation of English content, you don't need to care about them, and please don't delete them
提示: 英文内容只是中文内容的翻译, 你不需要关心它们, 也请不要删掉它们
Knowing that I now have an MVVM MAUI project. There is a BindingList<string> Names property in the ViewModel, which generates controls in the view like this:
已知我现在有一个 MVVM 的 MAUI 项目. 在 ViewModel 中有一个 BindingList<string> Names 属性, 它会在视图中这样进行生成控件:
<CollectionView ItemsSource="{Binding Names}">
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<Label Text="{Binding}"/>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
I want after each item, there is a Button that can be used to delete the current item, and the Button logic is called via Command.
That is to say, in the logic of this Command, operate the Names property in ViewModel and delete one of them, so this Command needs to be passed into ViewModel
我希望在每一项后, 都有一个 Button 可以用来删除当前项, 并且 Button 逻辑通过 Command 调用.
也就是说, 这个 Command 的逻辑中, 操作 ViewModel 中的 Names 属性, 删除其中一项, 所以这个 Command 需要传入 ViewModel
Normally, if you declare the Command directly in the ViewModel, and then instantiate it, create a new Command and pass in the current ViewModel object, so that the Command can manipulate the Names in the ViewModel
正常来讲, 如果直接在 ViewModel 中声明这个 Command, 然后实例化的时候, 新建 Command 并传入当前 ViewModel 对象, 这样 Command 就可以操作这个 ViewModel 中的 Names
However, for CollectionView, his DataTemplate binding cannot be bound to the properties of the BindingContext set by Page, and his BindingContext is each item in Names. In this way, the instance of Command cannot be obtained.
但是, 对于 CollectionView, 他的 DataTemplate 绑定是无法绑定到 Page 设定的 BindingContext 的属性的, 他的 BindingContext 是 Names 中的每一项. 这样就拿不到 Command 的实例
If you define it in Resource, you can get the Command instance through StaticResource, but how does ViewModel pass in this Command?
如果通过在 Resource 中定义的话, 倒是可以通过 StaticResource 来拿到 Command 实例, 但是, ViewModel 改如何传入这个 Command 呢?
I want to achieve this using MVVM design pattern
我想要使用 MVVM 设计模式实现这一目的

Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:MyViewModel}}, Path=DetailsCommand}"
You want this.
Edit: To clarify. You are tapping on a DataTemplate bound to specific model. With this you are telling to go search a parent of this what you are tapping (your ViewModel) and use its command. (Since your Model doesn't have any commands).

First, I noticed that you want to use the MVVM, so you can use the ObservableCollection instead of using BindingList. You can refer to this article about ObservableCollection.
Second, you can use the relative-bindings as the solution.
<Button Text="Delete"
Command="{Binding Source={RelativeSource AncestorType={x:Type local:MainPageViewModel}}, Path=DeleteItemCommand}"
CommandParameter="{Binding}"/>
Third, you can also set a name for your page and bind the name to your button source.
<Button Text="Delete" Command="{Binding BindingContext.DeleteCommand, Source={x:Reference MainPage}}" CommandParameter="{Binding .}" />

I hope I got your problem right.
The way out of the textbook:
Don't use "Binding Names" but bind to ViewModels again. The default should always be to bind to ViewModels.
But sometimes for me this was too much work and what you can do is to use the DataContext of a parent element. The nicest way for me it to give the root element a name and bind to this like:
{Binding DataContext.YourProperty, ElementName=YourRootElement}
With CommandParameter={Binding} you get your element.
[edit] You can also use FindAnchestor, but personally I don't likethat as it easily breaks when you change your XAML structure.
[edit2] It seems MAUI does not support binding by ElementName, at least I couldn't find it. So you will have to use the anchestor binding type https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/data-binding/relative-bindings#bind-to-an-ancestor
PS: Use BindingList only if your really need it, otherwise use ObservableCollection

Related

How to set event agrument converter in event trigger in Prism?

I use Prism in my project and I want to have a converter in the event trigger to convert the event argument to my class model.
The problem is that the converter is not called in the Prism implementation and sends the event arguments directly to the view model.
This is my XAML code:
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="RecordExpanding">
<prism:InvokeCommandAction Command="{Binding RowExpandCommand}"
CommandParameter="{Binding Mode=OneWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=events:RecordExpandingEventArgs}, Converter={StaticResource DataGridRecordExpandingConverter}}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
It does not work, because your assumptions about the parameter binding are wrong. The Prism InvokeCommandAction automatically assigns the event arguments of the associated event as command parameter, if you do not specify a parameter yourself. In the latter case it is overwritten.
Your parameter binding uses a RelativeSource. This is only suitable, if you want to bind to properties of an ancestor of your control in the logical tree or the control itself. This does not work with events, because they are not part of the tree.
However, there is a solution to your problem. You can specify which property of your event arguments will be passed as command parameter using the TriggerParameterPath property. Let us assume that your RecordExpandingEventArgs have a property called Record that you want to pass, then your action looks like this:
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="RecordExpanding">
<prism:InvokeCommandAction Command="{Binding RowExpandCommand}"
TriggerParameterPath="Record"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
Note that this also works for nested properties like Record.Cells. Alternatively, you can also use EventToCommand from MVVM Light framework, where you can explicitly specify an event arguments converter.

Passing a third party object as Command Parameter

I am using a 3rd party tree element in my XAML from which I want to add elements to my own tree control. This is done via a button that is bound to a Command "TransferClick".
Now to get the elements from the 3rd party tree control I need to ideally pass it along as a command parameter, which is where I'm stuck.
My XAML looks like this:
<Window>
<Window.DataContext>
<this:MeasurementConfig />
</Window.DataContext>
<Grid>
<SystemStorageUI:StorageChannelAndAliasBrowser x:Name="sdfBrowser"/>
<TreeView x:Name="fileTree"/>
<Button x:Name="trnsfrButton"
Command="{Binding TransferClick}"
CommandParameter="{Binding}"/>
</Grid>
</Window>
Now, when I use CommandParameter="{Binding}" I get an object of type MeasurementConfig in the "Execute" method of my RelayCommand : ICommand class. This makes sense as it's my DataContext.
When I use CommandParameter="{Binding sdfBrowser}" I gete null.
How can I pass the "sdfBrowser" object along?
You can set window name in your window tag and pass window as parameter through CommandParameter="{Binding ElementName=YourWindowName}". If you want only specific properties of your view you can implement interface with this specific properties in your view than bind window in CommandParameter as above but in viewModel get interface as parameter.
It seems the ElementName Keyword was missing:
CommandParameter="{Binding ElementName=sdfBrowser}" worked.

Binding to a command that is in a static resource viewmodel without datacontext

I have a ViewModel which that is defined in my application resources, this ViewModel has a command called RunCommand
and in my MainWindow i am trying to bind that command to a button without setting the datacontext so i tried
<Button Command="{Binding Source={StaticResource ViewModel.RunCommand}}"/>
it showed an exception, however when i do the following things work fine
<Button DataContext="{Binding Source={StaticResource ViewModel}}" Command="{Binding RunCommand}"/>
what is wrong with the first part, and do i have to set the datacontext for such a simple task?
You are certainly not forced to change/set the DataContext just so you can bind a simple property.
Here's what you want
<Button Command="{Binding RunCommand, Source={StaticResource ViewModel}}"/>
Setting the datacontext is a good thing to do ... it takes away the voodoo of what object you're talking to. I believe all MVVM frameworks help you out with Locators, and when not using them, you can use your code behind.
It's just the way the language works.

Unset/Change Binding in WPF

How can I unset the binding applied to an object so that I can apply another binding to it from a different location?
Suppose I have two data templates binded to the same object reference.
Data Template #1 is the default template to be loaded. I try to bind a button command to a Function1 from my DataContext class:
<Button Content="Button 1" CommandParameter="{Binding }" Command="{Binding DataContext.Function1, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
This actually works and the function gets binded. However, when I try to load Data Template # 2 to the same object (while trying to bind another button command to a different function (Function2) from my DataContext class):
<Button Content="Button 2" CommandParameter="{Binding }" Command="{Binding DataContext.Function2, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
It doesn't work and the first binding is still the one executed. Is there a workaround to this?
EDIT (for better problem context):
I defined my templates in my Window.Resources:
<Window.Resources>
<DataTemplate DataType="{x:Type local:ViewModel1}">
<local:View1 />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModel2}">
<local:View2 />
</DataTemplate>
</Window.Resources>
The View1.xaml and the View2.xaml contain the button definitions that I described above (I want them to command the control of my process flow).
ViewModel1 and ViewModel2 are my ViewModels that implement the interface IPageViewModel which is the type of my variable CurrentPageViewModel.
In my XAML, I binded ContentControl to the variable CurrentPageViewModel:
<ContentControl Content="{Binding CurrentPageViewModel}" HorizontalAlignment="Center"/>
In my .CS, I have a list defined as List<IPageViewModel> PageViewModels, which I use to contain the instances of my two View Models:
PageViewModels.Add(new ViewModel1());
PageViewModels.Add(new ViewModel2());
// Set starting page
CurrentPageViewModel = PageViewModels[0];
When I try to change my CurrentPageViewModel to the other view model, this is when I want the new binding to work. Unfortunately, it doesn't. Am I doing things the right way?
If for some reason you are unable to use just two different DataTemplates, usually because the datatemplates are very large or complex, i suggest using ContentControl and DataTemplateSelector.
In your DataTemplates place another ContentControl, create 2 DataTemplates just containing your button, one with Function1 one with Function2. Create a DataTemplateSelector and set it on the initial ContentControl. The DataTemplateSelector now just need to select the proper template depending on a decision, for example the type of the item or a property on the item etc.
If you still want to unset binding you can do it from code like:
BindingOperations.ClearBinding(txtName, TextBox.TextProperty)
But TemplateSelector approach will be more efficient.

Select an item from checkedlistbox using wpf,mvvm

I am new to MVVM, I have a checkedlistbox in a view with the list of titles(have bound the exposed property in ViewModel to this checkedlistbox control)...
Here is my XAML code that populates the ListCheckBox -
<ListBox x:Name="lstCode" ItemsSource="{Binding Code,Mode=TwoWay}" Grid.Row="1" Style="{StaticResource ListBoxStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox x:Name="chkBox" IsChecked="{Binding IsChecked,Mode=TwoWay}" Content="{Binding Code_Name}" Margin="0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This control shows the correct list of items with checkboxes for each item in the listbox...
What should be the code in viewmodel to make it work in two way - while getting the codes from database, it should automatically selected the code from the listcheckedbox and when the user selects one or more codes, the viewmodel should be able to know the items selected...
In general, for TwoWay binding, you will need to implement the INotifyPropertyChanged interface on the ViewModel you want to bind to.
In this case, your ViewModel will have to provide a property that returns a collection that your view can bind to, e.g. an ObservableCollection.
This ObservableCollection already allows you to add, update, and delete items in that list in a way that automatically communicates the changes between View and ViewModel.
For the rest I suggest to start digging into MVVM depths. To fully take advantage of WPF's capabilities, you will need to understand the basics for yourself. A great starting point is this SO thread: MVVM: Tutorial from start to finish?

Categories