WPF MVVM dynamic collection of user controls in a wrap panel - wpf

I'm new to WPF but have created a window with a wrap panel that contains a collection of user control instances dynamically added from code behind. Each user control will ultimately display data from a row returned from a database call. I'd like to make this follow MVVM but am a little stuck on the architecture. I think I need to have a view model for the user control and a view model for the window that would possess an observablecollection of the user control view models. How do I get that bound to a wrap panel on the view side so that the wrap panel sees the collection of user control view models and knows to establish a user control for each instance in the collection?
I think once this is all bound properly I can make a background worker that at a regular interval queries the database and creates / updates the user control view model objects and if I am inheriting from INotifyPropertyChanged and firing property changed events in my user control view model everything should update based on the binding. Does that sound correct?
I've seen basic examples such as an observablecollection of strings bound to a list box but I'm having trouble applying this to a more complex case. Any suggestions as to a general architecture or where I should look to get started is much appreciated!

Basically, you need an enumerable control that has one item for each element in your ObservableCollection. The control's items will need to be templated to display the data using your custom control
To do this create an ObservableCollection which holds your data objects and use it as the ItemsSource for a ListBox. The ListBox will then need to be changed to display its items in a WrapPanel instead of the default layout. Modify the ItemTemplate of the ListBox to use your custom user control for each list item.
ViewModel:
public class WindowViewModel
{
public ObservableCollection<MyDatabaseObject> DatabaseObjects { get; set; }
}
public class MyDatabaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string DbName
{
get { return _dbName; }
set {
_dbName = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("dbName");
}
}
private _dbName;
}
Xaml:
<Grid>
<ListBox ItemsSource="{Binding DatabaseObjects}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<MyUserControl Title="{Binding DbName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>

Are you looking for the ItemsControl class? With ItemsControl.ItemTemplate set to a DataTemplate for your ItemUserControlViewModel (-> item user control view). And ItemsControl.ItemsPanel set to anItemsPanelTemplate with a WrapPanel.
ItemControl's ItemsSource property would be bound to your ObservableCollection<ItemUserControlViewModel> from WindowViewModel.

Related

Create multi panel application in WPF

I'm developing WPF MVVM application and I want to create a Window with many panels that changes when user choose another panel from navigation.
I've read this article but it's not working due to Can't put a Page in a Style error. I can't find any answer about how to create a WPF application that navigate through different panels in one single window, how I can achieve what I want using MVVM pattern?
You can place various panels in a Grid, sharing the same space (overlapping) and change Visibility to make "Visible" only the one you want shown.
I've used this thecnique same time ago, and is compatible with MVVM too.
I have created an ContentPresenter and bind it to the MainWindow ViewModel and set the DataTemplate for each ViewModels.
<ContentPresenter Name="WindowContent" Content="{Binding CurrentPageViewModel}"/>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:MainViewModel}">
<views:MainView />
</DataTemplate>
</Window.Resources>
And so when the binded property is changed, ContentPresenter display proper ViewModel and due to DataTemplate, the actual View.
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("CurrentPageViewModel"));
}
}
}
private IPageViewModel _currentPageViewModel;
Every ViewModel implements simple IPageViewModel interface so only ViewModels could be set as content of ContentPresenter.

Dynamic databinding ListBox + WPF + FlowDocument

I have a listBox, which I am trying to bind to an IList collection using ItemsSource. My problem scenario comes when each of my person object has a FlowDocument which I am trying to display in a richTextBox within the listBoxItem.
Imagine the performance degradation, when there are 1000 person objects,
Is there a way, I get to dynamically load the flowDocument / RichTextbox so that there is no performance impact.
Is there a way, I get to know which Items of the listbox are visible at any moment in time so that, I can dynamically bind the richtextbox with the flow document and when the scroll happens, I can clear the previous binding and apply binding to only those items that are visible.
<ListBox ItemsSource="{Binding PersonsCollection">
<ListBox.ItemTemplate>
<DataTemplate>
<RichTextBox Document="{Binding PersonHistory}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
thanks
public class Person
{
public FlowDocument PersonHistory{get;set}
}
You can separate the UI in two controls to improve the performance. Consider adding a unique attribute in person class like primary key in a database table.
public class Person
{
public long ID{get;set;}
public FlowDocument PersonHistory{get;set}
}
Now you can have one ListBox
<ListBox Name="PersonsListBox" ItemsSource="{Binding PersonsCollection"} DisplayMemberPath="ID" SelectionChanged="personsList_SelectionChanged">
</ListBox>
With which you bind the PersonsCollection and set DisplayMemberPath="ID" to show only ids in ListBox.
And you have a RichTextBox separately in your xaml.
<RichTextBox Name="personHistoryTextBox"/>
If you see I have added an event with ListBox as well. The SelectionChanged Event.
In your event you can do something like this.
private void personsList_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if(PersonsListBox.SelectedItem != null){
personHistoryTextBox.Document = (PersonsListBox.SelectedItem as Person).PersonHistory;
}
}

Combo box with submenu

How can I show the combo box like this one, in C#.net 2010
It is possible by combining two controls ComboBox and TreeView in one UserControl.
Although this control looks quite simple, the actual implementation isn’t clear and takes long time.
Here is the sequence of steps:
1) Custom TreeView and TreeViewItem. They provide the following functionality:
Allow to expand and select an item from a view model (It isn’t possible to select an item of the TreeView using other ways)
The event which is fired when a user clicks a TreeViewItem (so it will be possible to close the ComboBox)
2) Interface for an item of the ItemsSource collection
public interface ITreeViewItemModel
{
string SelectedValuePath { get; }
string DisplayValuePath { get; }
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
IEnumerable<ITreeViewItemModel> GetHierarchy();
IEnumerable<ITreeViewItemModel> GetChildren();
}
Members of this interface:
IsExpanded – allows to expand the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
IsSelected – allows to select the TreeViewItem from the bound view model. Must be implemented as the INotifyPropertyChanged property.
SelectedValuePath – the unique string (unique item id) that will be used to select and expand the treeview control.
DisplayValuePath – the content that will be displayed at the header when the combobox is closed
GetHierarchy – returns the item and its parents.
GetChildren – returns child items of the current item
Create the Combobox:
It is the most difficult part of the implementation. I was forced to create many methods to provide the connection between Combobox and TreeView.
But although there is many private methods, there is only two public properties:
SelectedItem – now it is possible to get or set this item and it will be selected in the treeview.
SelectedHierarchy – it wasn’t necessary to create this property, but it wasn’t difficult so I’ve decided to implement it. Use list of strings instead of the actual item.
Add it to a view and bind with a view model:
<UserControl.Resources>
<Windows:HierarchicalDataTemplate x:Key="treeViewDataTemplate"
ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Title}" />
</Windows:HierarchicalDataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<local:ComboBoxTreeView ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
ItemTemplate="{StaticResource treeViewDataTemplate}"
HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>
The ItemTemplate property is obligatory property and must be of the HierarchicalDataTemplate type.
Check this out https://vortexwolf.wordpress.com/2011/04/29/silverlight-combobox-with-treeview-inside/

how to build dynamic grid and binding to xaml using mvvm

I'm planning a WPF application which will build dynamic grid with textblocks in the viewmodel and then refresh interface (xaml) with the new grid.
I've done the firts step, but i have problems to refresh the view with the new grid.
Is there any example code of how to bind the grid to the xaml that I can have a look at?? I really can't figure this out!
Thanks
You may be approaching this slightly wrongly, hard to say from the question-
Generally to show a dynamic set of UI elements in MVVM you bind the ItemsSource property of an ItemsControl to an ObservableCollection. The ItemsControl ItemsTemplate property converts the YourViewModel object into a UIElement which can be a TextBlock or whatever style you want.
So as an example:
// model
class Person
{
public string Name {get; private set;}
}
// view model
class MainViewModel
{
public ObservableCollection<Person> People {get; private set;}
}
//view
<UserControl DataContext="{Binding MyMainViewModelObject}">
<ItemsControl ItemsSource="{Binding People}">
<ItemsControl.ItemsTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>/
</ItemsControl.ItemsTemplate>
</ItemsControl>
</UserControl>
I havent tested that code, it is just to illustrate. There are other ways of dissecting the problem into MVVM, it all depends on the situation. You would have to give more details for us to help you out with that. Rarely in WPF is there a need to use code to create or add UI elements to other UIElements etc.
A point to note more along the exact lines of the question however is that an ItemsControl can either bind to a bunch of regular objects and use it's template to create UIElements from them, OR it can bind to a list of UIElements, in which case the template is not applied (sounds like this is the situation you have).

How to bind an observable collection to Multiple user controls at runtime?

I am stucked at the part where I have to bind a collection to a dynamic usercontrol. Scenario is something like this.
I have a dynamic control, having a expander , datagrid, combobox and textbox, where combox and textbox are inside datagrid. There are already two collections with them. One is binded with combobox and another is binded with datagrid. When the item is changes in combox its respective value is set to its respective textbox, and so on. and this pair of value is then set to the collection binded with datagrid. A user can add multiple items.
Now the main problem is that all these things are happening inside a user control which is added dynamically, that is on button click event. A user can add desired numbers of user controls to the form.
problem is coming in this situtaion. Say I have added 3 controls. Now in 1st one if i add a code to the collection then it gets reflected in the next two controls too, as they are binded with same collection.
So, I want to know is there anyway to regenrate/rename the same collection so that the above condition should not arise.
It's hard to answer your question without seeing the bigger picture, however I have a feeling you are going about this the wrong way. It appears that you are adding instances of your user control directly from code. Instead of doing that, you should create some kind of ItemsControl in your XAML, and in its ItemTemplate have your user control. Bind that ItemsControl to a collection in your view model, and only manipulate that collection.
You should not be referring to visual controls in your view model or code behind. Whenever you find yourself referencing visual elements directly from code, it should raise a warning flag in your mind "Hey! There's a better way than that!"...
Example:
The view model:
public class ViewModel
{
public ObservableCollection<MyDataObject> MyDataObjects { get; set; }
public ViewModel()
{
MyDataObjects = new ObservableCollection<MyDataObject>
{
new MyDataObject { Name="Name1", Value="Value1" },
new MyDataObject { Name="Name2", Value="Value2" }
};
}
}
public class MyDataObject
{
public string Name { get; set; }
public string Value { get; set; }
}
The window XAML fragment containing the list box and the data template:
<Window.Resources>
...
<DataTemplate x:Key="MyDataTemplate">
<local:MyUserControl/>
</DataTemplate>
</Window.Resources>
...
<ListBox ItemsSource="{Binding MyDataObjects}"
ItemTemplate="{StaticResource MyDataTemplate}"
HorizontalContentAlignment="Stretch"/>
The user control:
<UniformGrid Rows="1">
<TextBlock Text="{Binding Name}"/>
<TextBlock Text="{Binding Value}" HorizontalAlignment="Right"/>
</UniformGrid>

Resources