Calling a command from item collection in PRISM/ MEF/ WPF - wpf

Suppose I have the following:
<Grid x:Name="root">
<ListBox ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button Command="{Binding ElementName=root, Path=DataContext.MyCommand}" />
<!---There are other UI elements here -->
</DockPanel/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
This code executes MyCommand when the button is clicked, but I also want to execute MyCommand when the user presses Enter Key while the row is selected (which means button is not in focus)...
How to best do this in WPF/MEF/PRISM?
I recognize that in code I can't cast the DataContext to (MyViewModel) because that would violate MEF, and in code behind I only know the viewmodel interface type IViewModel...
//code behind of the XAML file above
public IViewModel ViewModel
{
get;
set;
}
Note: I am thinking of doing this in code behind, but I'm not sure if the answer is even I should do it in the viewmodel...

This can be done using KeyBindings. Create a new KeyBidnign to your Window and associate a Command to it. More information on KeyBindings.
<ListBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding MyCommand}"/>
</ListBox.InputBindings>
The CanExecute method of your viewmodel should have a validation for Selected Row.
public class ViewModel
{
public ViewModel()
{
MyCommand = new DelegateCommand(MyCommandExecute, MyCommandCanExecute);
}
private void MyCommandExecute()
{
// Do your logic
}
private bool MyCommandCanExecute()
{
return this.SelectedRow != null;
}
public object SelectedRow { get; set; }
public DelegateCommand MyCommand { get; set; }
}

Related

Using ComboBox to set value of field from a source List<T> with two values - WPF

I need a combobox to list the items from a List with two fields :
AcctTypeID - This is an int (0 - 20).
AcctTypeName - This is a string -- ex. "Accounts Payable" (the cooresponding AcctTypeID is say 10 for Accounts Payable).
I need the ComboBox to show the AcctTypeName but send the AcctTypeID to the underlying field.
I promise I have spent hours seraching for this and trying every idea I can think of or find.
Use this XAML and set or bind the ItemsSource of the ComboBox to an IEnumerable<T> where T is your class with the AcctTypeName and AcctTypeID properties:
<ComboBox x:Name="cmb" DisplayMemberPath="AcctTypeName" SelectedValuePath="AcctTypeID" />
You get the selected value using the SelectedValue property:
int acctTypeID = (int)cmb.SelectedValue;
It is impossible to give an exact answer to your question without some additional explanations.
For example, if you are using MVVM, then your code should be implemented like this:
public class AcctTypeDto
{
public int AcctTypeId { get; set; }
public string AcctTypeName { get; set; }
}
public class AcctTypesViewModel
{
public ObservableCollection<AcctTypeDto> AcctTypes { get; }
= new ObservableCollection<AcctTypeDto>()
{
new AcctTypeDto(){AcctTypeId=123, AcctTypeName="Teen"},
new AcctTypeDto(){AcctTypeId=456, AcctTypeName="Five"},
new AcctTypeDto(){AcctTypeId=789, AcctTypeName="Seven"}
};
private RelayCommand _sendIdCommand;
public RelayCommand SendIdCommand => _sendIdCommand
?? (_sendIdCommand = new RelayCommand<int>(SendIdExecute));
private static void SendIdExecute(int parameter)
{
MessageBox.Show($"Id={parameter}");
}
private RelayCommand _sendAccCommand;
public RelayCommand SendAccCommand => _sendAccCommand
?? (_sendAccCommand = new RelayCommand<AcctTypeDto>(SendAccExecute));
private static void SendAccExecute(AcctTypeDto parameter)
{
MessageBox.Show($"Id={parameter.AcctTypeId}; Name={parameter.AcctTypeName}");
}
}
<FrameworkElement.DataContext>
<local:AcctTypesViewModel/>
</FrameworkElement.DataContext>
<UniformGrid Columns="1">
<ComboBox x:Name="comboBox" VerticalAlignment="Top" ItemsSource="{Binding AcctTypes}"
DisplayMemberPath="AcctTypeName"
SelectedValuePath="AcctTypeId"/>
<Button Content="Send Id" VerticalAlignment="Center"
Command="{Binding SendIdCommand}"
CommandParameter="{Binding SelectedValue, ElementName=comboBox}"/>
<Button Content="Send AcctType" VerticalAlignment="Center"
Command="{Binding SendAccCommand}"
CommandParameter="{Binding SelectedItem, ElementName=comboBox}"/>
</UniformGrid>
RelayCommand and RelayCommand<T> classes used.

How to bind and automatically refresh ItemsSource when I'm binding to some Entity from DbSet?

I'm trying to make use of EntityFramework and WPF data binding for the first time.
I have some ListBox. I have set ItemsSource to SomeDbContext.SomeEntity.ToList(); programatically and I have set my binding like this:
<ListBox Name="listbox" Margin="4" SelectedValuePath="Address" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Id}"></Label>
<Label Content="{Binding Address}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Click="ButtonTest_Click">Open</Button>
I'm adding new item to my DbSet and I expected that my list will refresh after SomeDbContext.SaveChanges(); method call, but it didn't.
My Window code behind:
DatabaseContext _dbContext = new DatabaseContext();
public MainWindow()
{
InitializeComponent();
lb.ItemsSource = _dbContext.Addresses.ToList();
// I have tried to set source to _dbContext
}
private void ButtonTest_Click(object sender, RoutedEventArgs e)
{
_dbContext.Addresses.Add(new Adresses() { Address = "192.168.1.2:502" });
_dbContext.SaveChanges();
}
Here is my Entity:
public class Adresses
{
public int Id { get; set; }
public string Address { get; set; }
}
My DbContext:
public class DatabaseContext : DbContext
{
public DbSet<Adresses> Addresses { get; set; }
}
What I am doing wrong?
I guess that my approach is wrong, because I'm creating new object when I'm setting ItemsSource to _dbContext.Addresses.ToList();, but I have no idea how to bind directly to my DbSet (or is it possible).

How do I Bind WPF Commands between a UserControl and a parent Window

I'l start by letting a picture do some talking.
So you see, I want to create a WPF user control that supports binding to a parent window's DataContext. The user control is simply a Button and a ListBox with a custom ItemTemplate to present things with a Label and a Remove Button.
The Add button should call an ICommand on the main view model to interact with the user in selecting a new thing (instance of IThing). The Remove buttons in the ListBoxItem in the user control should similarly call an ICommand on the main view model to request the related thing's removal. For that to work, the Remove button would have to send some identifying information to the view model about the thing requesting to be removed. So there are 2 types of Command that should be bindable to this control. Something like AddThingCommand() and RemoveThingCommand(IThing thing).
I got the functionality working using Click events, but that feels hacky, producing a bunch of code behind the XAML, and rubs against the rest of the pristine MVVM implementation. I really want to use Commands and MVVM normally.
There's enough code involved to get a basic demo working, I am holding off on posting the whole thing to reduce confusion. What is working that makes me feel like I'm so close is the DataTemplate for the ListBox binds the Label correctly, and when the parent window adds items to the collection, they show up.
<Label Content="{Binding Path=DisplayName}" />
While that displays the IThing correctly, the Remove button right next to it does nothing when I click it.
<Button Command="{Binding Path=RemoveItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
This isn't terribly unexpected since the specific item isn't provided, but the Add button doesn't have to specify anything, and it also fails to call the command.
<Button Command="{Binding Path=AddItemCommand, RelativeSource={RelativeSource AncestorType={x:Type userControlCommands:ItemManager }}}">
So what I need is the "basic" fix for the Add button, so that it calls the parent window's command to add a thing, and the more complex fix for the Remove button, so that it also calls the parent command but also passes along its bound thing.
Many thanks for any insights,
This is trivial, and made so by treating your UserControl like what it is--a control (that just happens to be made up from other controls). What does that mean? It means you should place DependencyProperties on your UC to which your ViewModel can bind, like any other control. Buttons expose a Command property, TextBoxes expose a Text property, etc. You need to expose, on the surface of your UserControl, everything you need for it to do its job.
Let's take a trivial (thrown together in under two minutes) example. I'll leave out the ICommand implementation.
First, our Window
<Window x:Class="UCsAndICommands.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:t="clr-namespace:UCsAndICommands"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<t:ViewModel />
</Window.DataContext>
<t:ItemsEditor Items="{Binding Items}"
AddItem="{Binding AddItem}"
RemoveItem="{Binding RemoveItem}" />
</Window>
Notice we have our Items editor, which exposes properties for everything it needs--the list of items it is editing, a command to add a new item, and a command to remove an item.
Next, the UserControl
<UserControl x:Class="UCsAndICommands.ItemsEditor"
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:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
We bind our controls to the DPs defined on the surface of the UC. Please, don't do any nonsense like DataContext=this; as this anti-pattern breaks more complex UC implementations.
Here's the definitions of these properties on the UC
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
Just DPs on the surface of the UC. No biggie. And our ViewModel is similarly simple
public class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ICommand AddItem { get; private set; }
public ICommand RemoveItem { get; private set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
AddItem = new DelegatedCommand<object>(
o => true, o => Items.Add(new Item()));
RemoveItem = new DelegatedCommand<Item>(
i => true, i => Items.Remove(i));
}
}
You are editing three different collections, so you may want to expose more ICommands to make it clear which you are adding/removing. Or you could cheap out and use the CommandParameter to figure it out.
Refer the below code.
UserControl.XAML
<Grid>
<ListBox ItemsSource="{Binding Things}" x:Name="lst">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ThingName}" Margin="3"/>
<Button Content="Remove" Margin="3" Command="{Binding ElementName=lst, Path=DataContext.RemoveCommand}" CommandParameter="{Binding}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Window.Xaml
<Window x:Class="MultiBind_Learning.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBind_Learning"
Title="Window1" Height="300" Width="300">
<StackPanel Orientation="Horizontal">
<Button Content="Add" Width="50" Height="25" Command="{Binding AddCommnd }"/>
<local:UserControl2/>
</StackPanel>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = new ThingViewModel();
}
}
ThingViewModel.cs
class ThingViewModel
{
private ObservableCollection<Thing> things = new ObservableCollection<Thing>();
public ObservableCollection<Thing> Things
{
get { return things; }
set { things = value; }
}
public ICommand AddCommnd { get; set; }
public ICommand RemoveCommand { get; set; }
public ThingViewModel()
{
for (int i = 0; i < 10; i++)
{
things.Add(new Thing() { ThingName="Thing" +i});
}
AddCommnd = new BaseCommand(Add);
RemoveCommand = new BaseCommand(Remove);
}
void Add(object obj)
{
things.Add(new Thing() {ThingName="Added New" });
}
void Remove(object obj)
{
things.Remove((Thing)obj);
}
}
Thing.cs
class Thing :INotifyPropertyChanged
{
private string thingName;
public string ThingName
{
get { return thingName; }
set { thingName = value; OnPropertyChanged("ThingName"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Predicate<object> _canExecute;
private Action<object> _method;
public event EventHandler CanExecuteChanged;
public BaseCommand(Action<object> method)
{
_method = method;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_method.Invoke(parameter);
}
}
Instead of Base command you can try RelayCommand from MVVMLight or DelegateCommand from PRISM libraries.
By default, your user control will inherit the DataContext of its container. So the ViewModel class that your window uses can be bound to directly by the user control, using the Binding notation in XAML. There's no need to specify DependentProperties or RoutedEvents, just bind to the command properties as normal.

How to use CollectionViewSource with design time data in Expression Blend?

I wonder how I can show design time data in Expression Blend that is located inside a SampleData.xaml using a CollectionViewSource? Before changing my code to use the CVS, I used an ObservableCollection. I was in the need to filter and sort the items inside there, thus I changed the code to use the CVS. Now my designer complains about not being able to fill the SampleData's NextItems with a proper structure to show up in Expression Blend. Here is some code I use inside the app:
MainViewModel.cs
class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
NextItems = new CollectionViewSource();
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
some functions to fill, filter, sort etc...
}
MainView.xaml:
<phone:PhoneApplicationPage
... some other stuff ...
d:DesignWidth="480"
d:DesignHeight="728"
d:DataContext="{d:DesignData SampleData/SampleData.xaml}">
<Grid
x:Name="LayoutRoot"
Background="Transparent">
<controls:Panorama>
<controls:PanoramaItem>
<ListBox ItemsSource="{Binding NextItems.View}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Image Source="{Binding Image}" />
<StackPanel>
<TextBlock Text="{Binding FullName}" />
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
</Grid>
</phone:PhoneApplicationPage>
SampleData.xaml
<local:MainViewModel
xmlns:local="clr-namespace:MyAppNamespace"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:swd="clr-namespace:System.Windows.Data;assembly=System.Windows" >
<local:MainViewModel.AllItems>
<local:ItemModel
FullName="Dummy"
Image="/Images/dummy.png" />
</local:MainViewModel.AllItems>
<local:MainViewModel.NextItems>
How to fill the CollectionViewSource's Source?
</local:MainViewModel.NextItems>
</local:MainViewModel>
So the question I can't find an answer to is how to fill the Source for NextItems in SampleDate.xaml? Any help would be much appreciated.
if you want to show sample data in the designer I would recommend you to do it from code. There are two ways of generating sample data for the Blend Designer or the VStudio designer:
From an XML file as you do.
From a c# class -> Best option
best option.
In WPF, in windows 8 and in WP7.5 and highger, you can access a propertie called:Windows.ApplicationModel.DesignMode.DesignModeEnabled making use of it you can seed your ObservableCollection from your view model:
public class MainViewModel
{
public MainViewModel()
{
AllItems = new ObservableCollection<ItemViewModel>();
if (DesignMode.DesignModeEnabled)
{
AllItems = FakeDataProvider.FakeDataItems;
}
NextItems.Source = AllItems;
}
public CollectionViewSource NextItems
{
get;
private set;
}
public ObservableCollection<ItemViewModel> AllItems
{
get;
private set;
}
}
In this way, if you change the model, you dont' have to regenerate an XML file, it's a little bit cleaner from a C# file. The FakeDataProvider is an static class where all design-time fake data are stored. So in you XAML the only thing you have to do is to bind your Listbox to the collection of your ViewModel.

Binding data to ComboBox WPF

I am newbie to WPF, and needs help to bind data into the ComboBox. The xaml file contains the tag below.
<UserControl x:Class="SKAT.Postfordeler.Client.UI.View.ChooseInboxView"
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"
mc:Ignorable="d"
d:DesignHeight="42" d:DesignWidth="598">
<Grid>
<StackPanel Orientation="Horizontal">
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" DataContext="{Binding}" />
<Label Content="Et job kører allerede i denne indbakke (1500 ud af 1700 poster behandlet)" Name="_currentProcess" Margin="5" Height="25" />
</StackPanel>
</Grid>
//Inbox class , this class was implemented in seperate project
namespace SKAT.Postfordeler.Shared.DataTypes
{
[DataContract]
public class Inbox
{
[DataMember]
public String Id { get; set; }
[DataMember]
public String Folder { get; set; }
[DataMember]
public Rule Rules { get; set; }
}
}
//This code is located in the controller, the Activate method will fire when the MainWindow was executed
public void Activate()
{
var configuration = _configurationManager.GetConfiguration();// this method gets the xaml file settings
_chooseInboxView.FillInboxes(configuration.Inboxes); // Inboxes data binds to combobox
}
and in the View code behind, I created a method to bind the data which contains a type of list
public void FillInboxes(List<Inbox> inboxes)
{
DataContext = inboxes;
}
But it won't works,Any help please?
I assume your Inbox class consists of two properties (for simplicity), but there may be any number of them:
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
You write a DataTemplate, for example:
<Grid.Resources>
<DataTemplate x:Key="InboxTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ID}"/>
<TextBlock>:</TextBlock>
<TextBlock Text="{Binding Path=Text}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
Then correct your ComboBox declaration like:
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" ItemsSource="{Binding}" ItemTemplate="{StaticResource InboxTemplate}" />
Finally you set DataContext of your ComboBox to your List<Inbox>:
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DataContext = inboxes;
}
EDIT: As you've asked for a simpler solution, you can just override ToString() method of your Inbox class:
protected override string ToString()
{
return ID.ToString() + ":" + Text;
}
Instead of DataContext={Binding} you should have ItemsSource={Binding}.
The data context for any frameworkelement in the visual tree is by default {Binding}.
<ComboBox Name="_currentInbox"
SelectedItem="Hoved"
Width="180"
Margin="5"
Height="22"
DisplayMemberPath="Name"
ItemSource="{Binding}" />
Also for the combobox to display text of the items correctly I suppose you need DisplayMemberPath too. I assumed the property from Inbox class that you need to display is Name. Please replace with your relevant property name.
If your Inbox class is like,
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
And if you do not want to change your xmal, the code behind method should be like this,
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DisplayMemberPath = "Text"; // To display the 'Text' property in the combobox dropdown
//_currentInbox.DisplayMemberPath = "ID"; // To display the 'ID' property in the combobox dropdown
_currentInbox.DataContext = inboxes;
}

Resources