Selection change/add items with ComboBox in WPF - c#

Simple questions can be the hardest sometimes. 3 things I am trying to understand;
1. Allow a selection change within a combobox to help populate items in 2nd combobox.
2. Clear items in 2nd box before populating items.
3. Adding items in 2nd box.
Note that this code worked on my WinForms code, but I am trying to convert it to WPF and understand that code.
Code:
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" Margin="170,56,0,0" VerticalAlignment="Top" Width="160">
<ComboBoxItem Content="Hospital"/>
</ComboBox>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" Margin="30,131,0,0" VerticalAlignment="Top" Width="300"/>
$ComboBox_Location.add_SelectionChanged{
switch ($ComboBox_Location.SelectedItem){
"Hospital"{
$ComboBox_Printer.Items.Clear();
$Hospital = Get-Printer -ComputerName \\bmh01-print01 | where {($_.Name -like “*BMH01*”) -and ($_.DeviceType -eq "Print")}
foreach($Name in $Hospital){
$ComboBox_Printer.Items.Add("$($Name.name)");
}
}
}
Thank you in advance! And if any of you have a website or cite I could go to to see the specific coding for WPF, any help will be appreciated!

why is this question not answered by anyone.Anyway I will do my best to explain you. Hope I am not late to answer this question.
In WPF, we follow MVVM pattern, So there are 3 parts Model, View and ViewModel.
In viewmodel, we need to inherit Icommand and create a CommandHandler, so that if there is any button click / Selction changed will sent via this command and the delegated eventhandler will be raised.
The CommandHandler Class
public class CommandHandler : ICommand
{
private Action<object> _action;
private bool _canExeute;
public event EventHandler CanExecuteChanged;
private bool canExeute
{
set
{
_canExeute = value;
CanExecuteChanged(this, new EventArgs());
}
}
public CommandHandler(Action<object> action,bool canExecute)
{
_action = action;
_canExeute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExeute;
}
public void Execute(object parameter)
{
_action(parameter);
}
}
This CommandHandler will be used in the ViewModel Class, and then the viewmodel will be set as Datacontext to view via XAML.
public class ViewModelBase : INotifyPropertyChanged
{
private List<String> _printer = new List<string>();
private bool _canExecute;
public ViewModelBase()
{
_canExecute = true;
}
public List<string> Printers
{
get { return _printer; }
set { _printer = value; }
}
private ICommand _SelectedItemChangedCommand;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public ICommand SelectedItemChangedCommand
{
get
{
return _SelectedItemChangedCommand ?? (_SelectedItemChangedCommand =
new CommandHandler(obj => SelectedItemChangedHandler(obj), _canExecute));
}
}
public void SelectedItemChangedHandler(object param)
{
var selectedItem = ((ComboBoxItem)param).Content;
switch (selectedItem)
{
case "Hospital":
Printers = new List<string>(); //clearing the list;
// Hospital = GetHospital();// - ComputerName \\bmh01 - print01 | where { ($_.Name - like “*BMH01 *”) -and($_.DeviceType - eq "Print")}
// Here I have added data hard coded, you need to call your method and assgin it to printers property.
Printers.Add("First Floor Printer");
Printers.Add("Second Floor Printer");
OnPropertyChanged(nameof(Printers));
break;
default:
Printers = new List<string>();
break;
}
}
}
The ViewModel class is also inheriting INotifyPropertyChanged, where we need to implement the event and raise it. Now we need to raise propertychanged event providing the property name which is changed using assignment. Therefore inside SelectionChangedCommand, we add Printer and then raise PropertyChanged Event by sending the Printers PropertyName in as Parameter.
The View, We can use either Window or UserControl, For this example I have used Window.
View:-
<Window x:Class="Combo2.MainScreen"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:local="clr-namespace:Combo2"
xmlns:ViewModel="clr-namespace:Combo2.Validate"
mc:Ignorable="d"
x:Name="Window1"
Title="MainScreen" Height="450" Width="800">
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Location" HorizontalAlignment="Left" Grid.Row="0" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Location" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="0" Grid.Column="1" >
<ComboBoxItem Content="Hospital"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Label Content="Printer Names" HorizontalAlignment="Left" Grid.Row="1" VerticalAlignment="Top" Grid.Column="0"/>
<ComboBox Name="ComboBox_Printer" HorizontalAlignment="Left" VerticalAlignment="Top" Width="160" Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Printers}" >
</ComboBox>
</Grid>
Now, As in winform we have click or selectionchanged event but in order to keep the designer separate from code, we are not directly coupling it with it. I mean to say write a selection changed event in code behind then we are not making justification to it. For more information click on https://www.tutorialspoint.com/mvvm/mvvm_introduction.htm which will give you more insight into MVVM.
Now if you notice, when there is a selection changed we have binded the Command Property to a Command Property present in the Viewmodel, which is possible using Interaction class.
So where did we link the view and viewmodel that it at the top of the xaml. Here the datacontext is bound to ViewmodelBase class(viewmodel)
<Window.DataContext>
<ViewModel:ViewModelBase/>
</Window.DataContext>
Now answer to your question
1)Allow a selection change within a combobox to help populate items in 2nd combobox.
The selectionChanged event is called which will call the Command method present in the ViewModelBase and popluate the Printers Property.
<i:InvokeCommandAction Command="{Binding SelectedItemChangedCommand}" CommandParameter="{Binding ElementName=ComboBox_Location, Path=SelectedItem}"/>
Now since the viewmodel is bound to view any change to the property is displayed in the 2nd dropdown. Now that I have cleared and added data in Printers property, when the 1st drop is selected based on the text if matches "Hospital" the printers are added to the Property and displayed in 2nd Drop down.
2) Clear items in 2nd box before populating items
Before adding data in Printers property, it is cleared by instantiating the List, in your case it could be any other class. Now to whether the selected data is Hospital, we need to send the SelectedItem using the Command Parameter , we cast the "param" with ComboBoxItem and got the content.
3) Adding items in 2nd box.
We sure did add the values in Printers property.
Hope this helps you !!

Related

Connect UIElements dynamically added in WPF using MVVM

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.

WPF bind a dynamic generated slider to function

First: Not a duplicate of Binding Button click to a method --- it's about button, and Relay command can't pass the arguments I need
Also, not a duplicate of How do you bind a Button Command to a member method? - it's a simple method with no arguments - nothing to do with my question.
Obviously (but just to make sure and avoid trolls) not a duplicate of this either Silverlight MVVM: where did my (object sender, RoutedEventArgs e) go?.
Now after clearing this (sorry, I am just really sick of being marked as "duplicate" by people who didn't understand my question), let's talk about the issue: :D
I am trying to bind a generated slider (using data template) to an event (value changed), I know it's impossible to bind an event and I must use ICommand, but I don't know how to get the event arguments to the command function, this is the xaml relevant code: (without the binding since it doesnt work)
<Slider Grid.Column="1" Grid.Row="1" Height="30" IsSnapToTickEnabled="True" Maximum="100" SmallChange="1" IsMoveToPointEnabled="True"/>
And this is the function I want it to be binded to:
public void vibrationSlider_move(object Sender, RoutedPropertyChangedEventArgs<double> e)
{
VibrationValue = (byte)e.NewValue;
SendPacket(cockpitType, (byte)Index.VibrationSlider, VibrationValue);
}
As you can see, I need to use the 'e' coming with the event, I have no idea how to reach it without using the "ValueChanged" slider event.
Notes:
Please don't tell me to add the "ValueChanged" attribute like this:
<Slider ValueChanged="VibrationSlider_move"/>
:)
It's a generated dynamic slider using DataTemplate with an observableCollection, the function isn't in the window.cs file, therefore just using an event is not possible.
Thank you.
You can use the MVVMLight Toolkit, which allows to send the EventArgs as CommandParameter to the ViewModel:
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<cmd:EventToCommand Command="{Binding ValueChangedCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your command.Execute method, you now get an object as parameter which you just have to parse to the correct type...
You could create an extension
public partial class Extensions
{
public static readonly DependencyProperty ValueChangedCommandProperty = DependencyProperty.RegisterAttached("ValueChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
{
var element = s as Slider;
if (element != null)
{
element.ValueChanged -= OnSingleValueChanged;
if (e.NewValue != null)
{
element.ValueChanged += OnSingleValueChanged;
}
}
}));
public static ICommand GetValueChangedCommand(UIElement element)
{
return (ICommand)element.GetValue(ValueChangedCommandProperty);
}
public static void SetValueChangedCommand(UIElement element, ICommand value)
{
element.SetValue(ValueChangedCommandProperty, value);
}
private static void OnSingleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
var element = sender as Slider;
var command = element.GetValue(ValueChangedCommandProperty) as ICommand;
if (command != null && command.CanExecute(element))
{
command.Execute(element);
e.Handled = true;
}
}
}
which then can be used in xaml as below.
<Slider Minimum="0" Maximum="100" local:Extensions.ValueChangedCommand="{Binding ValueChangedCommand}"/>
As #Philip W stated, you could use e.g. MVVMLight to help dealing with MVVM pattern and with your problem at hand.
You could, for example, have a XAML with DataTemplate and Slider like so:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight"
mc:Ignorable="d"
Title="MainWindow"
Height="250"
Width="250">
<Window.Resources>
<DataTemplate x:Key="SomeTemplate">
<StackPanel Margin="15">
<!-- Wrong DataContext can drive you mad!1 -->
<StackPanel.DataContext>
<local:SomeTemplateViewModel />
</StackPanel.DataContext>
<TextBlock Text="This is some template"/>
<Slider
Height="30"
IsSnapToTickEnabled="True"
Maximum="100"
SmallChange="1"
IsMoveToPointEnabled="True">
<!-- Bind/pass event as command -->
<i:Interaction.Triggers>
<i:EventTrigger EventName="ValueChanged">
<command:EventToCommand
Command="{Binding Mode=OneWay, Path=ValueChangedCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Slider>
<!-- Show current value, just for sake of it... -->
<TextBlock
Text="{Binding Value}"
FontWeight="Bold"
FontSize="24">
</TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
<ContentControl ContentTemplate="{StaticResource SomeTemplate}" />
</Window>
So basically you bind desired event to named Command and pass EventArgs to it as parameter. Then in your ViewModel, being the DataContext of you Slider, you handle the event-passed-as-command.
public class SomeTemplateViewModel : ViewModelBase
{
private double _value;
public SomeTemplateViewModel()
{
// Create command setting Value as Slider's NewValue
ValueChangedCommand = new RelayCommand<RoutedPropertyChangedEventArgs<double>>(
args => Value = args.NewValue);
}
public ICommand ValueChangedCommand { get; set; }
public double Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged(); } // Notify UI
}
}
This would give you something similar to this.
Since your slider is dynamically generated, nothing prevents you from adding your ValueChanged event at a later time:
XAML:
<Slider x:Name="slider" HorizontalAlignment="Left" Margin="10,143,0,0" VerticalAlignment="Top" Width="474" Grid.ColumnSpan="2" />
Code-behind:
public MainWindow()
{
InitializeComponent();
// it is a good idea to not allow designer to execute custom code
if (DesignerProperties.GetIsInDesignMode(this))
return;
slider.ValueChanged += Slider_ValueChanged;
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
// do your stuff here
}
Checking design mode is not simple in any context, as pointed out here.

How to switch views within one window from button in child view in WPF MVVM?

I have an issue with switching views in a WPF MVVM app.
When clicking on menu items defined in the main view, switching works fine.
When clicking on a button in a child view, switching does not work as expected.
If I set contentcontrol in child view (where the button is) as well as parent view,
the child view gets displayed mixed with previous displayed view, a button from one view and background from the one I want to switch to.
Without it, the debugger shows something happening, similar steps in the ViewModelBase class to what happened when choosing from the menu mentioned above but no visual changes in the window.
I have commands in a ViewmodelBase (that all viewmodels inherit from either directly or through a mainviewmodel) class that gets called from bindings such as in the XAML above.
CurrentViewModel is a property in ViewModelBase that is used to determine which view gets displayed. In the constructor of ViewModelBase i set commands for example:
CategoryVMCommand = new RelayCommand(() => ExecuteCategoryVMCommand());
(RelayCommand from the line above comes from the MVVM light framework,
although its not necessary for the solution to use that framework)
I found many tutorials and answers for similar problems, but couldnt get any of them to work. For example I tried, without success, using IOC for a similar problem in the below link:
MVVM Main window control bind from child user control
Here are some of the code involved and description of what Im doing:
Main Window:
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
<DockPanel Margin="0,0,0,50">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open" Command="{Binding CategoryVMCommand}"/>
<MenuItem Header="_Close"/>
<MenuItem Header="_Save"/>
</MenuItem>
<MenuItem Header="_New">
<MenuItem Header="_Create" Command="{Binding MainControlVMCommand}"/>
</MenuItem>
</Menu>
<StackPanel></StackPanel>
</DockPanel>
</Grid>
</Window>
Then I select Menu item New, the following view is displayed:
<UserControl x:Class="WpfApplication1.MainControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid>
<!--<ContentControl Content="{Binding CurrentViewModel, Mode=OneWay}" />-->
<TextBlock HorizontalAlignment="Left" Margin="10,20,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="39" Width="144" FontSize="24"><Run Language="en-gb" Text="TITLE"/><LineBreak/><Run Language="en-gb"/></TextBlock>
<Button Content="Open category" HorizontalAlignment="Left" Margin="10,136,0,0" VerticalAlignment="Top" Width="153" Height="63" Command="{Binding CategoryVMCommand}" />
<Button Content="Create new category" HorizontalAlignment="Left" Margin="10,218,0,0" VerticalAlignment="Top" Width="153" Height="63"/>
<ListBox HorizontalAlignment="Left" Height="145" Margin="293,136,0,0" VerticalAlignment="Top" Width="201" Background="#FFDDDDDD"/>
<TextBlock HorizontalAlignment="Left" Margin="293,107,0,0" TextWrapping="Wrap" Text="Recently Used" VerticalAlignment="Top" FontSize="18"/>
</Grid>
</UserControl>
button open category clicked, and Currentviewmodel set code executes (depending on ContenControl in MainControl view being commented out or not either
return or assigned), then The ExecuteCategoryCommand get executed. Then the line with the expected command in ViewModelBase constructor executes, although
either no change or the mixed result i mentioned originally
ViewModelBase class:
namespace ViewModel
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ViewModelBase _currentViewModel;
public ICommand CategoryVMCommand { get; private set; }
public ICommand MainControlVMCommand { get; private set; }
protected void NotifyPropertyChanged( String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public ViewModelBase()
{
MainControlVMCommand = new RelayCommand(() => ExecuteMainControlVMCommand());
CategoryVMCommand = new RelayCommand(() => ExecuteCategoryVMCommand());
}
public ViewModelBase CurrentViewModel
{
get
{
return _currentViewModel;
}
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
NotifyPropertyChanged("CurrentViewModel");
}
}
protected void ExecuteCategoryVMCommand()
{
CurrentViewModel = null;
CurrentViewModel = new CategoryVM();
}
protected void ExecuteMainControlVMCommand()
{
CurrentViewModel = null;
CurrentViewModel = new MainControlVM();
}
}
}
So my question is how can I click the button in the child view, send command from ViewModelBase, set CurrentViewModel, and successfully switch views within one window without any visual remains of the previously displayed view?
Thanks for any help.

Passing value from child window to parent window using WPF and MVVM pattern

I have parent window which has textBox called "SchoolName", and a button called "Lookup school Name".
That Button opens a child window with list of school names. Now when user selects school Name from child window, and clicks on "Use selected school" button. I need to populate selected school in parent view's textbox.
Note: I have adopted Sam’s and other people’s suggestion to make this code work. I have updated my code so other people can simply use it.
SelectSchoolView.xaml (Parent Window)
<Window x:Class="MyProject.UI.SelectSchoolView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Parent" Height="202" Width="547">
<Grid>
<TextBox Height="23" Width="192"
Name="txtSchoolNames"
Text="{Binding Path=SchoolNames, UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay}"
/>
<Label Content="School Codes" Height="28" HorizontalAlignment="Left"
Margin="30,38,0,0" Name="label1" VerticalAlignment="Top" />
<Button Content="Lookup School Code" Height="30" HorizontalAlignment="Left"
Margin="321,36,0,0" Name="button1" VerticalAlignment="Top" Width="163"
Command="{Binding Path=DisplayLookupDialogCommand}"/>
</Grid>
</Window>
SchoolNameLookup.xaml (Child Window for Look up School Name)
<Window x:Class="MyProject.UI.SchoolNameLookup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
Title="SchoolCodeLookup" Height="335" Width="426">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="226*" />
<RowDefinition Height="70*" />
</Grid.RowDefinitions>
<toolkit:DataGrid Grid.Row="0" Grid.Column="1" x:Name="dgSchoolList"
ItemsSource="{Binding Path=SchoolList}"
SelectedItem="{Binding Path=SelectedSchoolItem, Mode=TwoWay}"
Width="294"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="True"
SelectionMode="Single">
<Button Grid.Row="1" Grid.Column="1" Content="Use Selected School Name"
Height="23" Name="btnSelect" Width="131" Command="{Binding
Path=UseSelectedSchoolNameCommand}" />
</Grid>
</Window>
SchoolNameLookupViewModel
private string _schoolNames;
public string SchoolNames
{
get { return _schoolNames; }
set
{
_schoolNames= value;
OnPropertyChanged(SchoolNames);
}
}
private ICommand _useSelectedSchoolNameCommand;
public ICommand UseSelectedSchoolNameCommand{
get
{
if (_useSelectedSchoolNameCommand== null)
_useSelectedSchoolNameCommand= new RelayCommand(a =>
DoUseSelectedSchollNameItem(), p => true);
return _useSelectedSchoolNameCommand;
}
set
{
_useSelectedSchoolNameCommand= value;
}
}
private void DoUseSelectedSchoolNameItem() {
StringBuilder sfiString = new StringBuilder();
ObservableCollection<SchoolModel> oCol =
new ObservableCollection<SchoolModel>();
foreach (SchoolModel itm in SchollNameList)
{
if (itm.isSelected) {
sfiString.Append(itm.SchoolName + "; ");
_schoolNames = sfiString.ToString();
}
}
OnPropertyChanged(SchoolNames);
}
private ICommand _displayLookupDialogCommand;
public ICommand DisplayLookupDialogCommand
{
get
{
if (_displayLookupDialogCommand== null)
_displayLookupDialogCommand= new
RelayCommand(a => DoDisplayLookupDialog(), p => true);
return _displayLookupDialogCommand;
}
set
{
_displayLookupDialogCommand= value;
}
}
private void DoDisplayLookupDialog()
{
SchoolNameLookup snl = new SchoolNameLookup();
snl.DataContext = this; //==> This what I was missing. Now my code works as I was expecting
snl.Show();
}
My solution is to bind both the windows to the same ViewModel, then define a property to hold the resulting value for codes, lets call it CurrentSchoolCodes, Bind the label to this property. Make sure that CurrentSchoolCodes raises the INotifyPropertyChanged event.
then in the DoUseSelectedSchoolNameItem set the value for CurrentSchoolCodes.
For properties in your models I suggest you to load them as they are required(Lazy Load patttern). I this method your property's get accessor checks if the related field is still null, loads and assigns the value to it.
The code would be like this code snippet:
private ObservableCollection<SchoolModel> _schoolList;
public ObservableCollection<SchoolModel> SchoolList{
get {
if ( _schoolList == null )
_schoolList = LoadSchoolList();
return _schoolList;
}
}
In this way the first time your WPF control which is binded to this SchoolList property tries to get the value for this property the value will be loaded and cached and then returned.
Note: I have to say that this kind of properties should be used carefully, since loading data could be a time consuming process. And it is better to load data in a background thread to keep UI responsive.
The Solution Sam suggested here is a correct one.
What you didn't get is that you should have only one instance of you viewmodel and your main and child page should refer to the same one.
Your viewmodel should be instanciated once: maybe you need a Locator and get the instance there... Doing like this the code in your ctor will fire once, have a look at the mvvmLight toolkit, I think it will be great for your usage, you can get rid of those Classes implementing ICommand too...
You can find a great example of using that pattern here:
http://blogs.msdn.com/b/kylemc/archive/2011/04/29/mvvm-pattern-for-ria-services.aspx
basically what happens is this:
you have a Locator
public class ViewModelLocator
{
private readonly ServiceProviderBase _sp;
public ViewModelLocator()
{
_sp = ServiceProviderBase.Instance;
// 1 VM for all places that use it. Just an option
Book = new BookViewModel(_sp.PageConductor, _sp.BookDataService);
}
public BookViewModel Book { get; set; }
//get { return new BookViewModel(_sp.PageConductor, _sp.BookDataService); }
// 1 new instance per View
public CheckoutViewModel Checkout
{
get { return new CheckoutViewModel(_sp.PageConductor, _sp.BookDataService); }
}
}
that Locator is a StaticResource, in App.xaml
<Application.Resources>
<ResourceDictionary>
<app:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
in your views you refer you viewmodels trough the Locator:
DataContext="{Binding Book, Source={StaticResource Locator}}"
here Book is an instance of BookViewModel, you can see it in the Locator class
BookViewModel has a SelectedBook:
private Book _selectedBook;
public Book SelectedBook
{
get { return _selectedBook; }
set
{
_selectedBook = value;
RaisePropertyChanged("SelectedBook");
}
}
and your child window should have the same DataContext as your MainView and work like this:
<Grid Name="grid1" DataContext="{Binding SelectedBook}">

Attached Collection Items Losing Data Context

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.

Categories