WPF two comboboxes based on the same list - c#

I am fairly new to working with WPF and have this simple scenario which I am looking to implement:
I have two comboboxes, cmbSite and cmbLogFiles and I have a List<LogFileDirectory> which is defined as follows:
class LogFileDirectory
{
public List<System.IO.FileInfo> Files { get; private set; }
public string Name { get; private set; }
public string Path { get; private set; }
private LogFileDirectory() { }
public LogFileDirectory(string name, string path)
{
this.Name = name;
this.Path = path;
this.Files = new List<System.IO.FileInfo>();
if (System.IO.Directory.Exists(this.Path))
{
foreach (string file in System.IO.Directory.GetFiles(this.Path, "*.log", System.IO.SearchOption.TopDirectoryOnly))
this.Files.Add(new System.IO.FileInfo(file));
}
}
}
I have cmbSite bound to the Name property on the List<LogFileDirectory> in the code behind like this:
cmbSite.ItemsSource = _logFileInfo.WebServerLogFileDirectories;
cmbSite.SelectedValue = "Path";
cmbSite.DisplayMemberPath = "Name";
I would like cmbLogFiles bound to the Files property on the same List<LogFileDirectory> of the currently selected cmbSite and filtered to the entry LogFileDirectory object for the currently selected value of cmbSite, but I am really not quite sure how to do this without writing code in the ClickEvent handler of cmbSite (which seems like the wrong approach based on my WPF research) and rebinding cmbLogFiles to the select cmbSite LogFileDirectory.

Based on the thread that #Chris pointed me to in the comment above, the resolution was simple.
<ComboBox Name="cmbLogFiles" Width="140" ItemsSource="{Binding SelectedItem.Files, ElementName=cmbSite}" />
Where the ItemsSource property of cmbLogFiles specifies that the Binding will be the Files Property off of the SelectedItem object (which is defined as object of LogFileDirectory) and specified via the Element attribute to my other combobox (cmbSites).
I was able to remove all of the code behind by setting a DataContext on my window:
parserView = new Parser();
parserView.DataContext = new LogFileInfo("deathstar");
And then the subsequent XAML of the Parser window:
<Window x:Class="Zapora.UI.Parser"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Log File Parser" Height="350" Width="525">
<StackPanel Orientation="Horizontal" Height="26" VerticalAlignment="Top">
<Label Content="Web Site:"/>
<ComboBox Name="cmbSite" Width="180" ItemsSource="{Binding WebServerLogFileDirectories}" DisplayMemberPath="Name" SelectedValuePath="Path"/>
<Label Content="Files Available:"/>
<ComboBox Name="cmbLogFiles" Width="140" ItemsSource="{Binding SelectedItem.Files, ElementName=cmbSite}" />
</StackPanel>
</Window>

Related

C# wpf listbox not updating from ObservableCollection

I'm trying to get the databinding I need to work with a ListBox.
I've parsed some data from a text file to a ObservableCollection<ViewModel> but the data isn't updating in the ListBox.
Here's some information:
The data which is written to from the parser:
class MainData
{
private static ObservableCollection<GroupViewModel> groupModelList = new ObservableCollection<GroupViewModel>();
public static ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
What GroupViewModel holds (not everything but it's all the same):
class GroupViewModel : INotifyPropertyChanged
{
private GroupModel groupModel;
public event PropertyChangedEventHandler PropertyChanged;
public GroupViewModel()
{
groupModel = new GroupModel();
}
public string Name
{
get { return groupModel.name; }
set
{
if (groupModel.name != value)
{
groupModel.name = value;
InvokePropertyChanged("Name");
}
}
}
...
}
And what GroupModel Holds:
class GroupModel
{
public string name { get; set; }
}
This is how the parser adds new items to the GroupModelView:
if (split[0] == "group")
{
currentGroup = new GroupViewModel();
currentGroup.Name = split[1];
MainData.GroupModelList.Add(currentGroup);
}
I created a ListBox in my WPF application with these XAML options:
<Window x:Class="SoundManager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SoundManager.ViewModels"
xmlns:vm2="clr-namespace:SoundManager.Code"
Title="MainWindow" Height="720" Width="1280">
<Window.Resources>
<vm:MainViewModel x:Key="MainViewModel" />
<vm2:MainData x:Key="MainData" />
</Window.Resources>
<ListBox Grid.Row="2" Height="484" HorizontalAlignment="Left" Margin="12,0,0,0" Name="lbFoundItems" VerticalAlignment="Top" Width="201" ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList/Name}" />
but for some reason the data isn't updating in the UI (new items aren't added visibly in the UI).
I've been just getting started with the MVVM pattern and databinding and I can't figure out what I'm doing wrong.
Thanks in advance!
GroupModelList/Name is not a valid property path here. Setting it like that does not make the ListBox show the Name property of the data items in the GroupModelList collection.
You would instead have to set the ListBox's DisplayMemberPath property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}"
DisplayMemberPath="Name"/>
or set the ItemTemplate property:
<ListBox ItemsSource="{Binding Source={StaticResource MainData}, Path=GroupModelList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Moreover, the GroupModelList property should not be static:
class MainData
{
private ObservableCollection<GroupViewModel> groupModelList =
new ObservableCollection<GroupViewModel>();
public ObservableCollection<GroupViewModel> GroupModelList
{
get { return groupModelList; }
}
}
Then you might have MainData as a property in your view model, and bind the ListBox like this:
<ListBox ItemsSource="{Binding Source={StaticResource MainViewModel},
Path=MainData.GroupModelList}" .../>

WPF DataGrid with 2 DataGridComboBoxColumns update second combobox based on selection of first combobox

I have a Datagrid that is bound to a datasoruce. Also there are 2 DataGridComboBoxColumn's that are populated by a static List. The binding of the DataGridComboBoxColumn's are like:
<DataGridComboBoxColumn Header="Category" Width="150" SelectedValuePath="ID" SelectedValueBinding="{Binding Category, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="CategoryName" ItemsSource="{Binding ReturnCategories, Source={StaticResource Typeslist}}"/>
<DataGridComboBoxColumn Header="Sub Category" Width="150" SelectedValuePath="ID" SelectedValueBinding="{Binding SubCategory, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="CategoryName" ItemsSource="{Binding ReturnSubCategories, Source={StaticResource Typeslist}}"/>
When the datagrid is loaded the Main Category combobox is populated with the categories and also selected correctly based on the record set.
The second combobox is populated also. What I want to achieve is whenever I change the main category combobox the sub category combobox needs to load corresponding values based on the selection of main categories.
Currently the static resources of the main and sub categories doesn’t accept any parameters. Is it possible to pass a parameter to the sub category static resource so it could load the corresponding list.
The static resources are populated by the database on call from XAML.
static List<MainCategories> mainCatergoryBuffer = new List<MainCategories>();
static List<SubCategories> subCatergoryBuffer = new List<SubCategories>();
If I should change the subcategory content based on main category selection does this mean that the other rows values of the sub category will effected also?
How can I solve this issue?
Grid example:
EDIT
You won't be easily able to do that, these combo boxes do not inherit the data grid data context.
http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=EN-US&k=k(System.Windows.Controls.DataGridComboBoxColumn);k(VS.XamlEditor);k(TargetFrameworkMoniker-.NETFramework,Version%3Dv4.5)&rd=true
Binding in a WPF data grid text column
https://www.google.fr/search?q=Cannot+find+governing+FrameworkElement+or+FrameworkContentElement+for+target+element.&oq=Cannot+find+governing+FrameworkElement+or+FrameworkContentElement+for+target+element.&aqs=chrome..69i57j0l4.1185j0j7&sourceid=chrome&es_sm=122&ie=UTF-8
WPF Error: Cannot find governing FrameworkElement for target element
Advice : use the free data grid from Xceed, you won't run onto such issues
https://wpftoolkit.codeplex.com/
Here's a really simple example on how to achieve this,
Obivously you'll want to adapt to it to your current classes.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication5
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
DataContext = new MyObject();
}
}
internal class MyObject
{
public MyObject()
{
AvailableCategories = new ObservableCollection<Category>(new List<Category>(new[]
{
new Category
{
Name = "category1",
SubCategories = new List<Category>(new[]
{
new Category {Name = "subCategory1a"},
new Category {Name = "subCategory1b"}
})
},
new Category
{
Name = "category2",
SubCategories = new List<Category>(new[]
{
new Category {Name = "subCategory2a"},
new Category {Name = "subCategory2b"}
})
}
}));
}
public ObservableCollection<Category> AvailableCategories { get; private set; }
}
public class Category
{
public string Name { get; set; }
public List<Category> SubCategories { get; set; }
public override string ToString()
{
return String.Format("Name: {0}", Name);
}
}
}
And the XAML :
<Window x:Class="WpfApplication5.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:wpfApplication5="clr-namespace:WpfApplication5"
Title="MainWindow"
Width="300"
Height="300"
Loaded="Window_Loaded"
mc:Ignorable="d">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DataTemplateCategory" DataType="wpfApplication5:Category">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</Grid.Resources>
<StackPanel>
<ComboBox x:Name="ComboBox1"
ItemTemplate="{StaticResource DataTemplateCategory}"
ItemsSource="{Binding AvailableCategories}"
d:DataContext="{d:DesignData MyClass}" />
<ComboBox DataContext="{Binding ElementName=ComboBox1,
Path=SelectedItem}"
ItemTemplate="{StaticResource DataTemplateCategory}"
ItemsSource="{Binding Path=SubCategories}"
d:DataContext="{d:DesignData Category}" />
</StackPanel>
</Grid>
</Window>

WPF Binding Combox with different List and different SelectedValue

In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :
private string rockDensUnit;
public string RockDensity_Unit
{
get { return rockDensUnit; }
set
{
if (rockDensUnit != value)
{
rockDensUnit = value;
Changed("RockDensity_Unit");
}
}
}
In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.
ComboItems.cs
//**OBJECTS I USE FOR LIST OF IEMS**
// Class for kg, gm
public class KgGmItems
{
public ObservableCollection<string> KgGmList { get; set; }
public KgGmItems()
{
KgGmList = new ObservableCollection<string>();
KgGmList.Add("kg/m3");
KgGmList.Add("gram/cm3");
}
public string ValueSelected { get; set; } // Don't know if this is useful in my case
}
// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
public MtCmItems()
{
Dict = new Dictionary<string, string>
{
{"meter", "meter"},
{"centimeter", "centimeter"}
};
}
//...
}
XML i.e. ucStep2 View
<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
<ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
<ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.
In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.
I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.
By default the 1st item should be selected.
UPDATE
Added DependencyProperty in ucStep2.xaml.cs
public static readonly DependencyProperty RockDensityUnitProperty =
DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string RockDensity_Unit
{
get { return this.GetValue(RockDensityUnitProperty) as string; }
set { SetValue(RockDensityUnitProperty, value); }
}
XML
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}"
Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>
ERROR
Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", "
after FindAncestor
DOUBT
The RockDensity_Unit CLR property in Step2InfoData is untouched.
Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :
<UserControl x:Class="WellBore.ucStep2"
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:WellBore.Models"
xmlns:top="clr-namespace:WellBore"
mc:Ignorable="d"
d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">
Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />
This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:
ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();
Or better like this if you had a uniquely identifiable property:
ValueSelected = KgGmList.Where(item => item.Id == Id).Single()
With your string values, you should be able to set the selected item from code like this:
ValueSelected = "Some value";
UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />
The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.
In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.
Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:
<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />
Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.
UPDATE 2 >>>
Your error is because you have to add an XML namespace at the top of your XAML file... something like this:
xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"
Then you use it to reference your class like this:
...AncestorType={x:Type YourNamespace:ucStep2} ...
Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change
Register("RockDensity_Unit", typeof(string), typeof(UserControl),
to
Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),
Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.
Use a Dictionary.
XAML
<ComboBox ItemsSource="{Binding Dict}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Prop}"/>
Code Behind
public Dictionary< ValueType, string > Dict { get; private set; }
private ValueType _prop;
public ValueType Prop
{
get{ return _prop }
set
{
_prop = value;
NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
}
}
public ViewModel()
{
Dict = new Dictionary< ValueType, string >()
{
{ value1, string1 },
{ value2, string2 },
{ value3, string3 }
};
}

How to choose which ObservableCollection to bind to a listbox?

Is that even possible? I have two ObservableCollections. I want to bind and populate a listbox with one of them. For example let's say that we have 2 buttons - one for Twitter and one for Facebook. Clicking on a Facebook button it will populate listbox with friend's names from facebook observable collection and it will bind it. Clicking on Twitter it will populate listbox with Twitter followers and populate listbox and bind it.
How to choose which collection will be populated in listbox?
I would just use one observable collection and fill based on the users choice. You could also fill it with the names from both sources and have a filter to filter out one or the other (apparently you need a wrapper object where you can indicate whether the name is a facebook friend or twitter follower).
Edit: Here is some quick code example of how you can do it:
public interface ISocialContact
{
string Name { get; }
}
public class FacebookContact : ISocialContact
{
public string Name { get; set; }
public string FacebookPage { get; set; }
}
public class TwitterContact : ISocialContact
{
public string Name { get; set; }
public string TwitterAccount { get; set; }
}
Then in your data context:
public ObservableCollection<ISocialContact> Contacts { get; set; }
...
Contacts = new ObservableCollection<ISocialContact> {
new FacebookContact { Name = "Face", FacebookPage = "book" },
new TwitterContact { Name = "Twit", TwitterAccount = "ter" }
};
And in your xaml:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type local:FacebookContact}">
<TextBlock Text="{Binding FacebookPage}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:TwitterContact}">
<TextBlock Text="{Binding TwitterAccount}"/>
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding Contacts}" Width="100" Height="100"/>
</Grid>
This will apply the appropriate template to each object in your collection. So you can have collection with just facebook contacts or just twitter contacts or mixed.
Also note: You do not need the common interface. It will also work if you just make your ObservableCollection of type object. But given that they are being displayed by the same app in the same list box indicates that you can find some kind of common base and either can create a comon interface or base class.
In your ViewModel, create a property that exposes one or the other ObservableCollection, and swap it out when the button is clicked:
private ObservableCollection<string> _twitterFriendList;
private ObservableCollection<string> _facebookFriendList;
private ObservableCollection<string> _selectedFriendList;
public ObservableCollection<string> SelectedFriendList
{
get { return _selectedFriendList; }
set
{
if (value != _selectedFriendList)
{
_selectedFriendList = value;
RaisePropertyChanged("SelectedFriendList");
}
}
}
void TwitterButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _twitterFriendList;
}
void FacebookButton_Click(object sender, RoutedEventArgs e)
{
SelectedFriendList = _facebookFriendList;
}
Then in your XAML you can just bind to the property:
<ListBox ItemsSource="{Binding SelectedFriendList}"/>
A non-elegant way of accomplishing this is to put 2 listboxes in the same location and bind 1 to the twitter collection and the other to the facebook collection. Bind their visibility to a property that changes based upon the button clicks. Personally, I'd have 2 radio buttons and display the listbox based upon which one is selected.
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=TwitterCollection}" SelectedItem="{Binding Path=SelectedTwitterItem}" Visibility="{Binding Path=IsTwitterSelected, Converter={StaticResource visibilityConverter}}" />
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=FacebookCollection}" SelectedItem="{Binding Path=SelectedFacebookItem}" Visibility="{Binding Path=IsFacebookSelected, Converter={StaticResource visibilityConverter}}" />
<RadioButton Grid.Row="1" Grid.Column="0" GroupName="rdoOptions" Content="{Binding Path=TwitterLabel}" IsChecked="{Binding Path=IsTwitterSelected}" />
<RadioButton Grid.Row="1" Grid.Column="1" GroupName="rdoOptions" Content="{Binding Path=FacebookLabel}" IsChecked="{Binding Path=IsFacebookSelected}" />

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}">

Categories