How keep alive user control? - wpf

I have wizard project that works with ContentControl which contains user controls.
I do the instantiation through the XAML file at my main window:
<DataTemplate DataType="{x:Type ViewModel:OpeningViewModel}">
<view:OpeningView/>
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModel:SecondUCViewModel}">
<view:SecondUCView/>
</DataTemplate>
But when I navigate between the UC's it seems that the UC's aren't works like "keep alive", Every UC switching creates new instance. How can I avoid it?
I want create for every UC just one instance and navigate between those instances only without creating new instances.
I know how write singleton but my project based on MVVM and I'm quite new at WPF so I'm not sure what is the best way to do this.
Thanks
Update:
Here the code of the viewModel:
In the viewModel I have :
private ObservableCollection _pages = null;
private NavigationBaseViewModel _currentPage;
#endregion
#region Properties
public int CurrentPageIndex
{
get
{
if (this.CurrentPage == null)
{
return 0;
}
return _pages.IndexOf(this.CurrentPage);
}
}
public NavigationBaseViewModel CurrentPage
{
get { return _currentPage; }
private set
{
if (value == _currentPage)
return;
_currentPage = value;
OnPropertyChanged("CurrentPage");
}
}
private ICommand _NavigateNextCommand;
public ICommand NavigateNextCommand
{
get
{
if (_NavigateNextCommand == null)
{
_NavigateNextCommand = new RelayCommand(param => this.MoveToNextPage(), param => CanMoveToNextPage);
}
return _NavigateNextCommand;
}
}
private ICommand _NavigateBackCommand;
public ICommand NavigateBackCommand
{
get
{
if (_NavigateBackCommand == null)
{
_NavigateBackCommand = new RelayCommand(param => this.MoveToPreviousPage(), param => CanMoveToPreviousPage);
}
return _NavigateBackCommand;
}
}
private bool CanMoveToNextPage
{
get
{
return this.CurrentPage != null && this.CurrentPage.CanMoveNext;
}
}
bool CanMoveToPreviousPage
{
get { return 0 < this.CurrentPageIndex && CurrentPage.CanMoveBack; }
}
private void MoveToNextPage()
{
if (this.CanMoveToNextPage)
{
if (CurrentPageIndex >= _pages.Count - 1)
Cancel();
if (this.CurrentPageIndex < _pages.Count - 1)
{
this.CurrentPage = _pages[this.CurrentPageIndex + 1];
}
}
}
void MoveToPreviousPage()
{
if (this.CanMoveToPreviousPage)
{
this.CurrentPage = _pages[this.CurrentPageIndex - 1];
}
}
And the ContentControl which contains the UC`s binded to CurrentPage

You can do that by hardcoding the UserControls in XAML, instead of using DataTemplates. DataTemplates will create new Controls every time they are instantiated. However, since you use MVVM, you could also move all data you want persisted between the changes to the ViewModels, and make sure that the ViewModel objects are always the same. Then, the DataTemplates would still create new controls, but they would contain the same information as before.

I have recently come up against the same issue with my views in MVVM. Basically, I wanted to cache views that took a while to render. If you are familiar with the ViewModelLocator, this approach should be straight forward.
In the client (e.g. WPF) project I created a ViewLocator class that looked like this:
public class ViewLocator : ObservableObject
{
#region Properties
private View _myView;
public View MyView
{
get { return _myView; }
set { Set(() => MyView, ref _myView, value); }
}
#endregion Properties
#region Constructors
public ViewLocator()
{
RegisterViews();
}
#endregion Constructors
#region Private Methods
private void RegisterViews()
{
MyView = new View();
}
#endregion Private Methods
}
And to use this in a data template I specified the ViewLocator as an static application resource so only one instance is ever instantiated - in my case I put it in in App.xaml. To use the ViewLocator and it's "View" properties, I did the following:
<vl:ViewLocator x:Key="ViewLocator" />
<DataTemplate DataType="{x:Type vm:ViewModel}">
<ContentControl Content="{Binding Source={StaticResource ViewLocator}, Path=MyView}" />
</DataTemplate>
By doing it this way, each view is only instantiated once and can be reused.

Related

Populate ListBox on Selecting TreeView node in WPF using MVVM

I am developing an application in WPF in which I want to populate a ListBox on selecting a node of a TreeView. I have populated the TreeView using MVVM pattern. It actually contains the the drives (C:\, D:...) and their corresponding sub-folders. The sub-folders are the nodes. On selecting these nodes the respective files should be shown in the ListBox. I know the C# code to get all the files in a folders, I have also implemented the same. However, I am not getting any clue to map them, so that on selecting the nodes the files in them should get reflected in the ListBox.
Can any please help me in this regard? The application is being developed in MVVM pattern and I need in the same pattern itself.
First add a Files collection class to your folder class (used in the tree view)
public class FolderItem
{
// other class code
private ObservableCollection<File> _Files = null;
public ObservableCollection<File> Files
{
get
{
if (_Files == null) _Files = GetFiles();
return _Files;
}
set
{
_Files = value;
}
}
}
Then bind the listbox to the selected treeview item.
<ListBox ItemsSource="{Binding ElementName=myTreeView, Path=SelectedItem.Files}"/>
You might have quite a number of files and folders so I think I would be inclined to lazy load as much as I could.
That means the viewmodel doesn't need to go iterate through the entire hard drive initially but you need some way of acting when selecteditem changes.
You can't bind selecteditem to a viewmodel because it's read only.
I would therefore use a behaviour like:
Data binding to SelectedItem in a WPF Treeview
Bind a SelectedFolder using that.
In the setter of SelectedFolder go get the list of folders and files for that folder and fill the two collections. One is the child collection of that selecteditem - for it's folders.
The other is an observableCollection for the files to see in the listbox.
Make that a propfull and implement inotifyproprtychanged so when I set it to a new collection it notifies the ui.
Bind that collection to the itemssource of the listbox.
Basicly The MVVM pattern uses three Layers :
The Model : Basicly it contains the Model classes and the business logic to get and manipulate Data information.
The ViewModel : It acts as an intermediate layer between the Model and the Views, it is attached to the different views.
The views : The différent views of the app.
Here an example how to fill a Window with list of Drives and Files.
Class BindableBaseViewModel
namespace TalkRepeater.ViewModel
{
public class BindableBaseViewModel : DependencyObject,INotifyPropertyChanged
{
protected virtual void SetProperty<T>(ref T member, T val,[CallerMemberName] string propertyName = null)
{
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
Class ViewModel
public class FoldersControlViewModel : BindableBaseViewModel
{
private ObservableCollection<Folders> _listFolders;
private ObservableCollection<Folders> _listFiles;
public FoldersControlViewModel()
{
FoldersBusinessObject vbo =new FoldersBusinessObject()
vbo.FillFolders();
ListFolders = FileBusinessObject.ListFolders;
}
public ObservableCollection<Folders> ListFolders
{
get
{
return _listFolders;
}
set
{
_listFolders = value;
OnPropertyChanged("ListFolders");
}
}
public ObservableCollection<Folders> ListFiles
{
get
{
return _listFiles;
}
set
{
_listFiles = value;
OnPropertyChanged("ListFiles");
}
}
Public void FillListFiles()
{
/*ListFiles= Cod to fill ListFiles*/
}
}
Class BusinessObject
public class FoldersBusinessObject
{
private ObservableCollection<Folders> _ListFolders;
public void FillFolders()
{
/* ListFolders= Code To fill the collection ListFolders */
}
public ObservableCollection<Folders> ListFolders
{
get
{
return _ListFolders;
}
set
{
_ListFolders = value;
}
}
}
Foldersview
<Window x:Class="Foldersview"
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"
d:DesignHeight = "300" Width="1007" Height="606">
<Grid Margin="10" >
<Canvas x:Name="canvasFolders" Margin="-10,0,912,10">
<TreeView x:Name="TreevFolders" ItemsSource="{Binding Path=ListFolders, Mode=TwoWay}" Canvas.Top="5" Canvas.Left="17" Width="142" Height="561"
SelectedItemChanged="TreevFolders_SelectedItemChanged" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=ListFolders}">
<TextBlock Text="{Binding Path=FileName}">
</TextBlock>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Canvas>
<Canvas Margin="159,10,0,10">
<Listview x:Name="Listview1" ItemsSource="{Binding ListFiles, Mode=TwoWay}" >
</Listview>
</Canvas>
</Grid>
</Window>
Class Foldersview Code Behind
public partial class Foldersview : Window
{
private void TreevFolders_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
FoldersControlViewModel vmd = (FoldersControlViewModel)this.DataContext;
vmd.FillListFiles ();
}
}
Class Mainwindow
public class MainWindowViewModel : BindableBase
{
private FoldersControlViewModel FoldersviewModel;
public MainWindowViewModel()
{
FoldersviewModel = new FoldersControlViewModel();
Foldersview=new Foldersview();
Foldersview.Datacontext=FoldersviewModel;
}
}
Cordialy

How to Access SelectedItem in one Page on a Separate Page

I have two datagrids separately on two pages, say a parentgrid on a parent-page and a childgrid on a child-page. how to access the selecteditem of parent-page to the child-page ?
when the both the datagrids are placed on the same page, the selecteditem works. but when I place the grids separately on each page, it doesn't work.
XAML for the ParentPage
<Grid.Datacontext>
<local:MainViewModel/>
</Grid.Datacontext>
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Persons}" SelectedItem="{Binding SelectedHost, Mode=TwoWay}" SelectionChanged="DataGrid_SelectionChanged"/>
codebehind for the ParentPage
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ChildPage _page = new ChildPage();
this.NavigationService.Navigate(_page);
}
XAML for child page
<DataGrid x:Name="ChildDatagrid" Margin="12,104,81,266" ItemsSource="{Binding Details}"/>
MainViewModel
//Datacontext
public MainViewModel()
{
this.Persons = Person.GetPersons();
}
// for Person Datagrid
private ObservableCollection<Person> personValues;
public ObservableCollection<Person> Persons
{
get { return personValues; }
set { this.SetProperty<ObservableCollection<Person>>(ref this.personValues, value); }
}
//for the PersonDetails datagrid
public ObservableCollection<PersonDetails> Details
{
get
{
if (this.Selectedperson == null)
{
return null;
}
return this.LoadDetails(this.Selectedperson.PersonID);
}
}
// method to load the persondetails data
private ObservableCollection<PersonDetails> LoadDetails(int personID)
{
ObservableCollection<PersonDetails> details = new ObservableCollection<PersonDetails>();
foreach (PersonDetails detail in PersonDetails.GetDetails().Where(item => item.PersonID == personID))
{
details.Add(detail);
}
return details;
}
// SelectedPerson Property
private Person selectedPersonValue;
public Person Selectedperson
{
get { return selectedPersonValue; }
set
{
this.SetProperty<Person>(ref this.selectedPersonValue, value);
this.RaiseNotification("Details");
}
}
You should make a ViewModel or Object, pass them into both pages and bind your grids to it. This way they will stay in sync.
Alternate option is to use and EventAggregator to sent messages between your pages.
If you're using WPF you should take a look into Prism. A lot of build-in functionality exists.
EDIT: Post changed to reflect new information;
I've never used the NavigationService with WPF before, so I'm not 100% sure what is available to you, so apologies if I miss something.
However, if you move your Details Collection to your Child Form, and make it a standard property;
private ObservableCollection<PersonDetails> _Details;
public ObservableCollection<PersonDetails> Details {
get { return _Details; }
set {
if (value.Equals(_Details) == false)
{
_Details = value;
OnPropertyChanged("Details");
}
}
}
Assuming you have a reference to your MainViewModel, you can then navigate to your page using;
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ChildPage _page = new ChildPage();
_page.Details = MainViewModel.LoadDetails(PersonID);
this.NavigationService.Navigate(_page);
}
As a note, I don't like the idea of calling the Navigiate Service from the Code behind, as this makes the code messy and difficult to test.
Jesse Liberty has a nice post on using the MVVM Light Framework, and allowing the ViewModel to call the Navigate Service Directly;
http://jesseliberty.com/2012/01/17/calling-navigate-from-the-view-model/

Data bind to a portion of a collection

I have a static collection of items (say numbers from 1 to 100) that I'm presenting in a ComboBox. I can bind these items to the ComboBox no problem.
My question is how to bind a second ComboBox to a subset of those items. The behavior I want is to have the second ComboBox bound to the subset of items remaining after the first ComboBox is selected. For example the first ComboBox would show 1,2,3...,100. If the number 43 is selected in the first ComboBox then the second ComboBox should show 44,45,...,100.
How can this be accomplished and have the second ComboBox update if the first is changed without a lot of code-behind?
I would do this with a using MVVM pattern.
Create a class that implements INotifyChange and expose three Property.
ICollection FullCollection
int FirstIndex
ICollection PartialCollection
Use this class as DataContext for your Control and bind SelectedIndex of the first combo box to FirstIndex property, ItemSource of first combobox to FullCollection and ItemSource of second collection to PartialCollection (Be sure that SelectedIndex binding mode is Two Way).
Then on set of FirstIndex property set the PartialCollection property as you want.
Remember that you have to use NotifyPropertyChange on set method of each Properties.
Hope this help.
I'd go with MVVM design but if you want to just do testing and want to see quick results without going into design patterns then I'd suggest using something like CLINQ / BLINQ / Obtics framework to leverege the power of LINQ while keeping the results live for the combo box.
Since LoSciamano has already posted a reply on that (while i was posting this!) I won't dive into details of MVVM implementation
I would expose the first collection as an ObservableCollection<T> and the second collection as a bare IEnumerable. You can then use the default collection view to handle the events necessary to re-filter the sub collection.
class FilteredVM : ViewModelBase
{
public ObservableCollection<MyVM> Items { get; private set; }
public IEnumerable<MyVM> SubItems { get; private set; }
public FilteredVM()
{
this.Items = new ObservableCollection<MyVM>();
this.SubItems = Enumerable.Empty<MyVM>();
var view = CollectionViewSource.GetDefaultView(this.Items);
view.CurrentChanged += (sender, e) => { SetupFilter(); };
view.CollectionChanged += (sender, e) => { SetupFilter(); };
}
private void SetupFilter()
{
var view = CollectionViewSource.GetDefaultView(this.Items);
var current = view.CurrentItem;
if (current != null)
{
this.SubItems = this.Items.Where((vm,idx) => idx > view.CurrentPosition);
}
else
{
this.SubItems = Enumerable.Empty<MyVM>();
}
this.OnPropertyChanged("SubItems");
}
}
Alternatively, if you'd like to keep CollectionViewSource out of your VM:
class FilteredVM : ViewModelBase
{
private MyVM selectedItem;
public MyVM SelectedItem
{
get { return this.selectedItem; }
set
{
if (value != this.selectedItem)
{
this.selectedItem = value;
this.OnPropertyChanged("SelectedItem");
this.SetupFilter();
}
}
}
public ObservableCollection<MyVM> Items { get; private set; }
public IEnumerable<MyVM> SubItems { get; private set; }
public FilteredVM()
{
this.Items = new ObservableCollection<MyVM>();
this.SubItems = Enumerable.Empty<MyVM>();
this.Items.CollectionChanged += (sender, e) => { this.SetupFilter(); };
}
private void SetupFilter()
{
if (this.SelectedItem != null)
{
var item = this.SelectedItem; // save for closure
this.SubItems = this.Items.SkipWhile(vm => vm != item).Skip(1);
}
else
{
this.SubItems = Enumerable.Empty<MyVM>();
}
this.OnPropertyChanged("SubItems");
}
}
Keep in mind this will require SelectedItem to be properly bound in the View to the ViewModel. The first approach listed allows the SelectedItem to be bound to anywhere (or nowhere).
Have 2 Observable collections so that when item in the 1st box is selected, it will kick off method which clears and re populates the 2nd collection. As it is observableCollection, it gets reflected in the WPF GUI automatically
Here is a concrete example (as usual, may be improved, but the idea is here) :
Code behind an view model :
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Initialsource = new ObservableCollection<int>();
for (int i = 0; i < 101; i++)
{
Initialsource.Add(i);
}
}
private int _selectedsourceItem;
public int SelectedsourceItem
{
get { return _selectedsourceItem; }
set
{
_selectedsourceItem = value;
SubsetSource = new ObservableCollection<int>(Initialsource.Where(p => p > _selectedsourceItem));
InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
InvokePropertyChanged(new PropertyChangedEventArgs("SelectedsourceItem"));
}
}
private ObservableCollection<int> _initialsource;
public ObservableCollection<int> Initialsource
{
get { return _initialsource; }
set
{
_initialsource = value;
InvokePropertyChanged(new PropertyChangedEventArgs("Initialsource"));
}
}
private ObservableCollection<int> _subsetSource;
public ObservableCollection<int> SubsetSource
{
get { return _subsetSource ?? (_subsetSource = new ObservableCollection<int>()); }
set
{
_subsetSource = value;
InvokePropertyChanged(new PropertyChangedEventArgs("SubsetSource"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
}
XAML :
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Horizontal">
<ComboBox Width="100" ItemsSource="{Binding Initialsource}" SelectedItem="{Binding SelectedsourceItem, Mode=TwoWay}"></ComboBox>
<ComboBox Width="100" ItemsSource="{Binding SubsetSource}"></ComboBox>
</StackPanel>
</Grid>
You can use my ObservableComputations library:
ObservableCollection<Item> itemsSubset = ItemsObservableCollection
.Skiping(fromIndex)
.Taking(itemsInSubsetCount);
itemsSubset reflacts all the changes in ItemsObservableCollection.

How to save data from a DetailView bound to a ViewModel if the repository is a no-go in a viewmodel?

we mvvm lovers all know Josh Smith mvvm sample and how he has saved the customer in the detail customer view by injecting the repository object into the customerViewModel`s constructor.
But a viewmodel should not know about repositories. Its just a model of a view nothing must being aware of persistence etc...
How can I register my Action delegate SaveDocumentDelegate on the DocumentViewModel if its set in the code-behind? Actually I should subscribe the delegate in my DocumentController but how can I instantiate the DocumentView in my DocumentController and set it as Datacontext not doing that in code-behind. Only thing that came to my mind is using a contentcontrol in the window and bind it to the type of the viewModel and datatemplate it with the Document UserControl like this:
<UserControl.Resources>
<DataTemplate DataType="{x:Type ViewModel:DocumentViewModel}">
<View:DocumentDetailView/>
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{Binding MyDocumentViewModel}" />
But I do not want to use a control to solve my architectural problems...
xaml:(view first approach)
public partial class DocumentDetailView : UserControl
{
public DocumentDetailView()
{
InitializeComponent();
this.DataContext = new DocumentViewModel(new Document());
}
}
DocumentViewModel:
public class DocumentViewModel : ViewModelBase
{
private Document _document;
private RelayCommand _saveDocumentCommand;
private Action<Document> SaveDocumentDelegate;
public DocumentViewModel(Document document)
{
_document = document;
}
public RelayCommand SaveDocumentCommand
{
get { return _saveDocumentCommand ?? (_saveDocumentCommand = new RelayCommand(() => SaveDocument())); }
}
private void SaveDocument()
{
SaveDocumentDelegate(_document);
}
public int Id
{
get { return _document.Id; }
set
{
if (_document.Id == value)
return;
_document.Id = value;
this.RaisePropertyChanged("Id");
}
}
public string Name
{
get { return _document.Name; }
set
{
if (_document.Name == value)
return;
_document.Name = value;
this.RaisePropertyChanged("Name");
}
}
public string Tags
{
get { return _document.Tags; }
set
{
if (_document.Tags == value)
return;
_document.Tags = value;
this.RaisePropertyChanged("Tags");
}
}
}
UPDATE:
public class DocumentController
{
public DocumentController()
{
var win2 = new Window2();
var doc = new DocumentViewModel(new DocumentPage());
doc.AddDocumentDelegate += new Action<Document>(OnAddDocument);
win2.DataContext = doc;
wind2.ShowDialog();
}
private void OnAddDocument(Document doc)
{
_repository.AddDocument(doc);
}
}
What do you think about that idea?
But a viewmodel should not know about
repositories. Its just a model of a
view nothing must being aware of
persistence etc...
The viewmodel connects the model and view together; it is exactly what controls persistence, though it does not handle persistence.
We decouple this from other concern by using services.
One way to avoid adding persistence concerns to the viewmodel is by abstracting those concerns into repository interfaces, so that we can inject it as a dependency. In this way we can delegate persistence work in the viewmodel, usually in response to the user's interaction with the view.

Silverlight DataGrid - Binding to a Collection of Collections of objects

Problem: The data I'm trying to display is essentially a collection of collections of some object. So the rows can be any number, normal for a datagrid, and the columns are also any number. That's not so normal for a datagrid. Usually you have a set number of columns and your rows vary. The datagrid cell will be either a string or a value changeable via a combobox.
Attempted Solution: I tried adding columns to the datagrid dynamically, and while this worked just fine (adding them in codebehind) the real issue I hit was how to bind to the underlaying objects. The data is being built dynamically and I tried a couple of formats. I tried a collection of Arrays, and also an ObservableCollection of ObservableCollections. I could bind to the objects but as Bindings in Silverlight have to bind to properties I couldn't come up with a solution presenting the data this way.
My solution as a result has been to display the data in a more traditional manner, with a list and a datagrid. When you select an item in the list it repopulates the data in the datagrid to show the objects.
Question: is there a way to bind a datagrid cell to a collection of collections of objects?
I found this question (WPF) which looks similar and it didn't help any. I think it's the same issue.
WPF DataGrid: DataGridComboxBox ItemsSource Binding to a Collection of Collections
One possible solution is instead of using a simple collection as the inner object, create a class derived from a collection and implement ICustomTypeDescriptor on it. In the interface implementation, iterate over the elements of the collection and populate your property descriptor collection accordingly. Once you do that, you should be able to bind to those properties from XAML.
Example - a data object based on a dictionary, which you can bind against its key names (I compressed to a single line all trivial method implementations):
class DictionaryDataObject : Dictionary<string, object>, ICustomTypeDescriptor
{
#region ICustomTypeDescriptor Members
public AttributeCollection GetAttributes() { return AttributeCollection.Empty; }
public string GetClassName() { return "DictionaryDataObject"; }
public string GetComponentName() { return null; }
public TypeConverter GetConverter() { return null; }
public EventDescriptor GetDefaultEvent() { return null; }
public PropertyDescriptor GetDefaultProperty() { return null; }
public object GetEditor(Type editorBaseType) { return null; }
public EventDescriptorCollection GetEvents(Attribute[] attributes) { return EventDescriptorCollection.Empty; }
public EventDescriptorCollection GetEvents() { return EventDescriptorCollection.Empty; }
public PropertyDescriptorCollection GetProperties() { return GetProperties(null); }
public object GetPropertyOwner(PropertyDescriptor pd) { return this; }
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
var pds =
this.Keys
.Select(x => new DictionaryPropertyDescriptor(x))
.ToArray();
return new PropertyDescriptorCollection(pds);
}
#endregion
}
class DictionaryPropertyDescriptor : PropertyDescriptor
{
public DictionaryPropertyDescriptor(string name) : base(name, null) { }
public override bool CanResetValue(object component) { return false; }
public override Type ComponentType { get { return null; } }
public override bool IsReadOnly { get { return false; } }
public override Type PropertyType { get { return typeof(object); } }
public override void ResetValue(object component) { }
public override bool ShouldSerializeValue(object component) { return false; }
public override object GetValue(object component)
{
var dic = component as DictionaryDataObject;
if (dic == null) return null;
return dic[Name];
}
public override void SetValue(object component, object value)
{
var dic = component as DictionaryDataObject;
if (dic == null) return;
dic[Name] = value;
}
}
Sample object setup from code behind:
DictionaryDataObject ddo = new DictionaryDataObject();
public Window4()
{
ddo["propa"] = 1;
ddo["propb"] = "foo";
ddo["propc"] = "bar";
ddo["propd"] = 4.5;
InitializeComponent();
DataContext = ddo;
}
XAML usage:
<Window.Resources>
<DataTemplate x:Key="template">
<WrapPanel>
<TextBlock Text="{Binding propa}" Margin="5"/>
<TextBlock Text="{Binding propb}" Margin="5"/>
<TextBlock Text="{Binding propc}" Margin="5"/>
<TextBlock Text="{Binding propd}" Margin="5"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource template}"/>
</Grid>
If you want to adapt this solution to a list instead of a dictionary, you must set each property name based on some property of the list element.
I think I understand what you are trying to accomplish and I actually have a more elegant solution to your problem and it does not involve writing any custom classes. I wrote a blog post on this issue. The blog is geared towards the DataGrid from the Silverlight Toolkit, but you can easily modify it to use any grid.
The solution is here.
Let me know if this is what you were looking for.

Resources