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;
}
}
Related
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.
I would like to set up a datagrid so that whenever an item is added to its itemssource the datagrid scrolls down to show the last item.
The datagrid is inside a datatemplate, so i cannot set the X:name property and access it directly from the codebehind.
What I have in mind is to use a datagrid event that fires when a row is added and has the grid scroll itself.
Here's some psuedo code that outlines how I have things set up:
UI.XAML exerpt
<TabControl ItemsSource="{Binding Parents}" x:Name="ProductsTab">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Key}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<DataGrid Margin="5" ItemsSource="{Binding Value.Children}">
<DataGrid.Columns>
<Column Column definitions removed for your sanity/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
UI.XAML.CS exerpt
public class UI
{
//Thanks to Dr. WPF for the ObservableDictionary class
public ObservableDictionary<string, Parent> Parents {get; set;}
}
Parent.CS
public class parent
{
public ObservableCollection<Child> Children {get; set;}
}
The datagrids are not editable.
In case you're wondering, I have read the post "How to autoscroll on WPF datagrid" the code in that post would work for me if I could find an event that fires whenever an item is added to the datagrid itemssource.
Any Ideas?
Combine the autoscrolling idea with the idea from this question or this MSDN thread: instead of listening to your grid's event to detect row additions, listen to the events from the ItemsSource.
Edit: Since you don't like that suggestion, you could try hooking LoadingRow, but I strongly suspect this will require EnableRowVirtualization = false in order to work (I haven't tried it). If your collection gets large, turning off row virutalization opens up the possibility of a serious performance hit.
you can access the DataGrid, even if it is in a DataTemplate, by doing a 'search' in the visual tree : VisualTreeHelper.GetChildCount // VisualTreeHelper.GetChild , then test again the type until you find your grid. And you might seek with same kind of methods the ScrollBar, and then you can hook an event handler and use a code-behind logic.
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).
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
I'm trying to change the layout of a databound treeview from this:
To this:
And of course selection must works properly:
Do you have any ideas about how to do that. I've been trying to change the template but I can't find out a way to have this behavior. Maybe a component already exists...
Thanks for your help !
This is difficult. It seems to need a HierarchicalDataTemplate, but because the behavior you want requires multiple ItemsControls, it is not going to work as expected. I don't think there is a way to create a TreeView template in XAML that will do this. Your best bet is to create a custom items control of some sort. You will probably need to do the items binding in code, rather than in XAML, because without the HierarchicalDataTemplate the XAML has no way of understanding nested relationships.
That being said, if you are guaranteed to only have 2 levels of nesting (as in your example), you could do this easily with the following mark-up:
<Window.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</Window.Resources>
<StackPanel Orientation="Horizontal">
<ListBox Name="Level1" Width="150" Height="150"
ItemsSource="{Binding Collection}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level2" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level1, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
<ListBox Name="Level3" Width="150" Height="150"
ItemsSource="{Binding ElementName=Level2, Path=SelectedValue.Children}"
ItemTemplate="{StaticResource ItemTemplate}"/>
</StackPanel>
Where Collection is your root items collection and there is a property on each item called Children containing the child collection.
But I think what you are asking for is an items control that can support any number of nested levels, not just 2. So in that case, I would do this in code-behind. The binding will be the same- that is, at each level, the ListBox should be bound to the parent level's items. But you will obviously need to iterate and create one ListBox for each nested level.
I finally find a way out, but like you say Charlie, it involves creating ListBox:
I create a new CustomControl which inherits Control (I couldn’t use neither Selector or TreeView because I wouldn’t have been able to manage the SelectedItem property from the derived class)
In the template of this CustomControl is an ItemsControl. This ItemsControl has its ItemTemplate property set to a DataTemplate containing a ListBox.
The CustomControl has a Depth property of type int. This property indicates the number of ListBox that should be generated.
The CustomControl automatically databound ListBoxes together: each ListBox’s ItemsSource property is databound to the SelectedItem’s children property of the previous ListBox in the visual tree.
The CustomControl has a SelectedItem property and a SelectionChanged event (like Selector-derived class).
I added an IsReallySelected attached property to the ListBoxItem which are generated. This enables to databing an IsSelected property of the ViewModel class behind the control with the IsSelected of the ListBoxItem. I had to create an attached property because its value is true when the ListBoxItem is selected AND the parent ListBox has IsSelectionActive set to true.
I blogged about this solution (with source code) on my blog.
Its too bad I didn't notice this question before you went to all that work. It is easy to restyle a TreeView to appear this way: The only code required is a single very simple attached property, "VisibleWhenCurrentOf".
The way to do it is to:
Style TreeViewItem to include a ListBox in its ControlTemplate outside the ItemsPresenter.
Control the visibility of the TreeViewItem template using "VisibleWhenCurrentOf", so that a given item is only visible inside the ItemsPresenter if it is the current item within the ListBox.
Restyling details
Here is the XAML for the relevant templates:
<ControlTemplate TargetType="TreeView">
<DockPanel>
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
<ControlTemplate TargetType="TreeViewItem">
<DockPanel
local:VisibilityHelper.VisibleWhenCurrentOf="{Binding ItemsSource, RelativeSource={RelativeSource FindAncestor,HeaderedItemsControl,2}">
<ListBox
ItemsSource="{TemplateBinding ItemsSource}"
IsSyncrhonizedWithCurrentItem="true"
Style="{DynamicResource BoxesTreeViewBoxStyle}"
ItemTemplate="{Binding HeaderTemplate}"
ItemTemplateSelector="{Binding HeaderTemplateSelector}" />
<ItemsPresenter />
</DockPanel>
</ControlTemplate>
These two templates are identical except for the conditional visibilty. The way this works is that the "+" in front of the tree item becomes a ListBox, and all items except the one selected in the ListBox are hidden.
Your BoxesTreeViewBoxStyle should set a margin around the ListBox so they will space correctly. You can actually simplify this further by putting the ListBox property values in the style, but I find it more convenient to set them in the ControlTemplate so I can restyle the ListBox without having to remember these settings.
Attached property
Here is the code for the VisibleWhenCurrentOf attached property:
public class VisibilityHelper : DependencyObject
{
// VisibleWhenCurrentOf
public static object GetVisibleWhenCurrentOf(DependencyObject obj) { return (object)obj.GetValue(VisibleWhenCurrentOfProperty); }
public static void SetVisibleWhenCurrentOf(DependencyObject obj, object value) { obj.SetValue(VisibleWhenCurrentOfProperty, value); }
public static readonly DependencyProperty VisibleWhenCurrentOfProperty = DependencyProperty.RegisterAttached("VisibleWhenCurrentOf", typeof(object), typeof(VisibilityHelper), new UIPropertyMetadata
{
PropertyChangedCallback = (sender, e) =>
{
var element = sender as FrameworkElement;
if(e.OldValue!=null)
{
var oldView = e.OldValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
oldView.CurrentChanged -= UpdateVisibilityBasedOnCurrentOf;
if(e.NewValue==null) element.DataContextChanged -= UpdateVisibilityBasedOnCurrentOf;
}
if(e.NewValue!=null)
{
var newView = e.NewValue as ICollectionView ?? CollectionViewSource.GetDefaultView(e.OldValue);
newView.CurrentChanged += UpdateVisibilityBasedOnCurrentOf;
if(e.OldValue==null) element.DataContextChanged += UpdateVisibilityBasedOnCurrentOf;
}
UpdateVisibilityBasedOnCurrentOf(sender);
}
});
static void UpdateVisibilityBasedOnCurrentOf(object sender, DependencyPropertyChangedEventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender, EventArgs e) { UpdateVisibilityBasedOnCurrentOf(sender); }
static void UpdateVisibilityBasedOnCurrentOf(object sender)
{
var element = sender as FrameworkElement;
var source = GetVisibleWhenCurrentOf(element);
var view = source==null ? null : source as ICollectionView ?? CollectionViewSource.GetDefaultView(source);
var visible = view==null || view.CurrentItem == element.DataContext;
element.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
}
}
There is nothing complex here: Any time DataContext or the view's Current changes, visibilty is recomputed. The PropertyChangedCallback simply sets event handlers to detect these conditions and the UpdateVisibiltyBasedOnCurrentOf handler recomputes visibility.
Advantages of this solution
Since this solution is a real TreeView:
You get all the selection handling functionality for free.
It works with any number of tree levels.
You can use all the features of HierarchicalDataTemplate, including HeaderTemplate and HeaderTemplateSelector
You can use different ItemsSource bindings at each level rather than every collection requiring a "Children" proerty
It is a lot less code than a custom control
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>