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;
}
}
I have a TabControl in an MVVM WPF application. It is defined as follows.
<TabControl Style="{StaticResource PortfolioSelectionTabControl}" SelectedItem="{Binding SelectedParameterTab}" >
<TabItem Header="Trades" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailTradeRegion" cal:RegionManager.RegionName="NSDetailTradeRegion" />
</TabItem>
<TabItem Header="Ccy Rates" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Margin="0,10,0,5" Name="NSDetailCcyRegion" cal:RegionManager.RegionName="NSDetailCcyRegion" />
</TabItem>
<TabItem Header="Correlations / Shocks" Style="{StaticResource PortfolioSelectionTabItem}">
<ContentControl Name="NSDetailCorrelationRegion" cal:RegionManager.RegionName="NSDetailCorrelationRegion" />
</TabItem>
<TabItem Header="Facility Overrides" Style="{StaticResource PortfolioSelectionTabItem}" IsEnabled="False">
<ContentControl Name="NSDetailFacilityOverrides" cal:RegionManager.RegionName="NSDetailFacilityOverrides" />
</TabItem>
</TabControl>
So each tab item content has its own view associated with it. Each of those views has the MEF [Export] attribute and is associated with the relevant region through view discovery, so the above code is all I need to have the tab control load and switch between them. They all reference the same shared ViewModel object behind them and so all interact seamlessly.
My problem is that when the user navigates to the parent window, I want the tab control to default to the second tab item. That is easy enough to do when the window is first loaded, by specifying in XAML IsSelected="True" in TabItem number 2. It is less easy to do when the user navigates away from the screen and then comes back to it.
I thought about having a SelectedItem={Binding SelectedTabItem} property on the tab control, so I could programmatically set the selected tab in the ViewModel, but the problem is I have no knowledge of the TabItem objects in the ViewModel as they are declared above in the XAML only, so I have no TabItem object to pass to the setter property.
One idea I had was to make the child Views (that form the content of each of the tab items above) have a style on the UserControl level of their XAML, something along the following.
<Style TargetType={x:Type UserControl}>
<Style.Triggers>
<DataTrigger Property="IsSelected" Value="True">
<Setter Property="{ElementName={FindAncestor, Parent, typeof(TabItem)}, Path=IsSelected", Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
I know the findancestor bit isn't correct; I've just put it there to specify my intent, but I am not sure of the exact syntax. Basically for each UserControl to have a trigger that listens to a property on the ViewModel (not sure how I would distinguish each different UserControl as obviously they can't all listen to the same property or they would all select simultaneously when the property is set to True, but having a property for each usercontrol seems ugly) and then finds its parent TabItem container and sets the IsSelected value to true.
Am I on the right track with a solution here? Is it possible to do what I am pondering? Is there a tidier solution?
If you look at the TabControl Class page on MSDN, you'll find a property called SelectedIndex which is an int. Therefore, simply add an int property into your view model and Bind it to the TabControl.SelectedIndex property and then you can select whichever tab you like at any time from the view model:
<TabControl SelectedIndex="{Binding SelectedIndex}">
...
</TabControl>
UPDATE >>>
Setting a 'startup' tab is even easier using this method:
In view model:
private int selectedIndex = 2; // Set the field to whichever tab you want to start on
public int SelectedIndex { get; set; } // Implement INotifyPropertyChanged here
Just FYI,
I gone through the same issue where I add tabs dynamically using ObservableCollection source but last added Tab do not get selected.
I have done same changes what Sheridan said to select Tab as per SelectedIndex. Now last added Tab gets selected but it was not getting focused.
So to focus the Tab we have to add set Binding IsAsync property True.
<TabControl ItemsSource="{Binding Workspaces}" Margin="5" SelectedIndex="{Binding TabIndex, Mode=OneWay,UpdateSourceTrigger=PropertyChanged, IsAsync=True}">
The below code sample will create a dynamic tab using MVVM.
XAML
<TabControl Margin="20" x:Name="tabCategory"
ItemsSource="{Binding tabCategory}"
SelectedItem="{Binding SelectedCategory}">
<TabControl.ItemTemplate>
<DataTemplate>
<HeaderedContentControl Header="{Binding TabHeader}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding TabContent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Modal Class
TabCategoryItem represents each tab item. On two properties, TabHeader will display a tab caption and TabContent contains the content/control to fill in each tab.
Public Class TabCategoryItem
Public Property TabHeader As String
Public Property TabContent As UIElement
End Class
VM Class
Public Class vmClass
Public Property tabCategory As ObjectModel.ObservableCollection(Of TabCategoryItem)
Public Property SelectedCategory As TabCategoryItem
End Class
The below code will fill and bind the content. I am creating two tabs, tab1 and tab2. Both tabs will contain text boxes. You can use any UIelement instead of text boxes.
Dim vm As New vmClass
vm.tabCategory = New ObjectModel.ObservableCollection(Of TabCategoryItem)
'VM.tabCategory colection will create all tabs
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab1", .TabContent = new TextBlock().Text = "My first Tab control1"})
vm.tabCategory.Add(New TabCategoryItem() With {.TabHeader = "Tab2", .TabContent = new TextBlock().Text = "My first Tab control2"})
mywindow.DataContent = vm
The accepted answer is not working with DependencyObject on your ViewModel .
I'm using MVVM with DependencyObject and Just setting the TabControl didn't work for me.The problem I had was the the property was not getting update on the View when I was setting the tab selectedIndex from the ViewModel.
I did set the Mode to be two ways but nothing was working.
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
The ViewModel property "SelectedTab" was getting updated all the time when I navigated between tabs. This was confirming my binding was working properly. Each time I would navigate the tabs both the Get and Set would get called in my ViewModel. But if I try to set the SelectedIndex in the ViewModel it would not update the view.
ie: SelectedTab=0 or SelectedTab=1 etc...
When doing the set from the ViewModel the SelectedTab 'set' method would be called, but the view would never do the 'get'.
All I could find online was example using INotifyPropertyChanged but I do not wish to use that with my ViewModel.
I found the solutions in this page: http://blog.lexique-du-net.com/index.php?post/2010/02/24/DependencyProperties-or-INotifyPropertyChanged
With DependencyObject, you need to register the DependencyProperties. Not for all properties but I guess for a tabcontrol property you need to.
Below my code:
view.xaml
//Not sure below if I need to mention the TwoWay mode
<TabControl SelectedIndex="{Binding SelectedTab,Mode=TwoWay}" >
...
</TabControl>
ViewModel.cs
public class ViewModel : DependencyObject
{
public static readonly DependencyProperty SelectedTabDP = DependencyProperty.Register("SelectedTab", typeof(int), typeof(ViewModel));
public int SelectedTab
{
get { return (int)GetValue(SelectedTabDP); }
set { SetValue(SelectedTabDP, value); }
}
}
Basically all I had to do was to actually register the dependency property (DependencyProperty) as you can see above.
What made this hard to figure out was that I have a bunch of other Properties on that view and I didn't need to register them like that to make it work two ways. For some reason on the TabControl I had to register the property like I did above.
Hope this help someone else.
Turns out my problem were because my components have names:
x:Name="xxxxxxxx"
Giving names to components at the same time of biding them with DependencyObject seems to be the main cause of all my issues.
In order to improve semantic of my viewmodel and to not work with an int when using code to check for the selected tab, I made some additions to the accepted answer so to use an Enum instead of an int.
These are the steps:
Define an Enum representing the different tabs:
public enum RulesVisibilityMode {
Active,
History
}
Expose the SelectedTab as a property using the enum instead of the int:
public RulesVisibilityMode SelectedTab { get; set; }
Create a converter to convert from an int to your enum (I don't need the ConvertBack because I never select the active tab from the code, but you can add it too):
internal class RulesVisibilityModeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException("Conversion from visibility mode to selected index has not been implemented");
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int selectedTabIndex;
if (int.TryParse(value.ToString(), out selectedTabIndex))
{
return (RulesVisibilityMode)selectedTabIndex;
}
return null;
}
}
Bind the tabcontrol to the SelectedTab property through the converter:
<TabControl SelectedIndex="{Binding SelectedTab, Mode=OneWayToSource, Converter={StaticResource RulesVisibilityModeConverter}}" ...
Now every time you need to check for the selected tab in the code you deal with a readable enum:
if (this.SelectedTab != RulesVisibilityMode.Active) ...
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.
Is there a way to bind a Silverlight control to an object (or database table's row) which contains the values of several control's properties, doing so without by define the binding for each property?
For instance:
Let's say I have the class (or entity based on database table's row) with the following values:
class TextBlockValues
{
public string Text{get; set;}
public string HorizontalAlignment{get; set;}
public string VerticalAlignment{get; set;}
}
I want to bind it to a TextBlock in my silverlight application (again without explicit specify the binding for each property).
Thank you for your time.
There are two parts in a binding: DataContext and the actual Binding objects. Once you set up the data context for an item, all the properties, and children will automatically use that.
For example:
<TextBlock Name="CaptionText" Text="{Binding Text}" HorizontalAlignment="{Binding HorizontalAlignment}" Height="20" TextAlignment="Center" FontStretch="Expanded" FontSize="13" />
And in the .cs file:
CaptionText.DataContext = myObject;
If I understand your question right the answer is no. Even though you can set the control's DataContext you still have to bind which property in the control binds to what in the class.
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>