Issue with TabControl which ContentTemplate contains a TabControl - wpf

In my WPF application, I have a TabControl named ParentTabControl, which ContentTemplate is composed of several controls, including a TabControl named ChildTabControl.
ParentTabControl contains, say, 2 tabs (each one bound to a different source), where as ChildTabControl always contain 1 tab. I'm focusing the first tab of ParentTabControl. By default, the first (the only one) tab of ChildTabControl is selected. The problem is that, if I switch to the second tab of ParentTabControl, the tab of its ChildTabControl is not selected. Is that a normal behavior? How can I do to always select a tab?
I hope I'm clear enough. Here is some code:
ParentTabControl:
<TabControl Name="ParentTabControl"
ItemsSource="{Binding ParentItemsSource}"
ContentTemplate="{StaticResource ResourceKey=ContentTemplate}"
IsSynchronizedWithCurrentItem="True" />
ContentTemplate:
<DataTemplate x:Key="ContentTemplate">
<TabControl Name="ChildTabControl"
ItemsSource="{Binding ChildItemsSource, Converter={StaticResource ResourceKey=ItemToObservableCollectionConverter }}" />
</DataTemplate>
ItemsSource:
public ObservableCollection<ParentData> ParentItemsSource { get; set; }
public class ParentData
{
public ChildData ChildItemsSource { get; set; }
}
Converter:
public class ItemToObservableCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return new ObservableCollection<object> { value };
}
}
Thank you.

First, if you have one and only one item, why do you want a TabControl? If you just want to have an element with a header, you can do that in your ContentTemplate.
In order to fix the issue you're seeing, however, have you tried setting SelectedIndex="0" on the ChildTabControl? That should force it to always be selected.
EDIT for other possible solution
Well, I was able to replicate this, but it only happens intermittently in my test.
I think the easiest way you can fix this is to set the SelectedItem of the child TabControl to the ChildItemsSource:
<TabControl Name="ChildTabControl"
ItemsSource="{Binding ChildItemsSource, Converter={StaticResource ResourceKey=ItemToObservableCollectionConverter }}"
SelectedItem="{Binding ChildItemsSource}" />
I have no idea what is causing this issue, but this definitely fixes it.

Related

WPF tab control and MVVM selection

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) ...

Make only one of many controls visible based on value of another control

I have a ComboBox bound to a collection of objects defined as this.
public class TierOption
{
public string Option { get; set; }
public Type DataType { get; set; }
}
public class TierOptions : ObservableCollection<Tier1Option>
{
}
I have 3 other controls related to this ComboBox, which are a TextBox, ComboBox, or a WPFToolKit:DatePicker.
I need to show only the related control which corresponds to the datatype(Type) of the object selected in the first ComboBox and neither of the others.
Pseudo Code Example:
(Probably too close to butchered C# but hopefully it conveys the idea)
switch (ComboBox.SelectedItem.DataType)
{
case String:
TextBox.Visibility = Visibility.Visible;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Hidden;
break;
case DateTime:
TextBox.Visibility = Visibility.Hidden;
ComboBox.Visibility = Visibility.Hidden;
DatePicker.Visibility = Visibility. Visible;
break;
<...so forth and so on...>
}
My attempts have resulted in very non-wpf looking convoluted messes which don't work regardless. Being new to wpf I'm trying very hard to stay true to the best design practices.
Thank you!
You can play with DataTemplate with DataType property
<...Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type DateTime}">
<DatePicker .../>
</DataTemplate>
...
</...Resources>
<ContentControl Content="{Binding SelectedItem, ElementName=myComboBox}"/>
<ComboBox ItemsSource="{Binding ...}"/>
The code above is just the idea, you could have to make adjustements. For example you won't be able to modify a string item itself (you could have to encapsulate each item of your list)
If your list contains all items of the same type, you can use a ContentTemplateSelector on contentControl.
ContentControl Content="{Binding SelectedItem, ElementName=YourCombBox}" ContentTemplateSelector="{StaticResource YourTemplateSelector}"
MSDN DOC about ContentControl.ContentTemplateSelector Property
Bind to the detail visibility to ElementName=ComboBox Path=SelectedItem.DataType. And you will need to use a converter that returns visibility. You will need two converters return opposite answers. If you have more than 2 combination then some more in the line of Jonas.
I assumed Type was a system class and it appears to be a custom class. Extend that class to have additional properties. Even if Type was a system type you could just create a class that implements it and extend it.
public Visibility TextBoxVisibility { get; }
public Visibility ComboBoxVisibility { get; }
...
Then on TextBox bind the visibility
Visisbility="{binding ElementName=Combobox Path=SelectedItem.DataType.TextBoxVisibility]";

What techniques can I employ to create a series of UI Elements from a collection of objects using WPF?

I'm new to WPF and before I dive in solving a problem in completely the wrong way I was wondering if WPF is clever enough to handle something for me.
Imagine I have a collection containing objects. Each object is of the same known type and has two parameters. Name (a string) and Picked (a boolean).
The collection will be populated at run time.
I would like to build up a UI element at run time that will represent this collection as a series of checkboxes. I want the Picked parameter of any given object in the collection updated if the user changes the selected state of the checkbox.
To me, the answer is simple. I iterate accross the collection and create a new checkbox for each object, dynamically wiring up a ValueChanged event to capture when Picked should be changed.
It has occured to me, however, that I may be able to harness some unknown feature of WPF to do this better (or "properly"). For example, could data binding be employed here?
I would be very interested in anyone's thoughts.
Thanks,
E
FootNote: The structure of the collection can be changed completely to better fit any chosen solution but ultimately I will always start from, and end with, some list of string and boolean pairs.
I would strongly recommend the ItemsControl, its behaviour is as close as you can get to the ASP.Net repeater control so it is very flexible.
Declare the item control as:
<ItemsControl Name="YourItemsControl"
ItemsSource="{Binding Path=YourCollection}"
ItemTemplate="{StaticResource YourTemplate}">
</ItemsControl>
Then you can use the datatemplate to organise the data into a display format for the user
<DataTemplate x:Key="ProjectsTemplate">
<StackPanel Margin="0,0,0,10">
<Border CornerRadius="2,2,0,0" Background="{StaticResource ItemGradient}" d:LayoutOverrides="Width, Height">
<local:ItemContentsUserControl Height="30"/>
</Border>
...
Useful ItemsControl Links
http://drwpf.com/blog/itemscontrol-a-to-z/
http://www.galasoft.ch/mydotnet/articles/article-2007041201.aspx
I hope this helps you.
You can use Data Templates. Here's a good post about it.
This is exactly the kind of scenario WPF simplifies. Event-handlers- bah! Data-binding and data templates make this a cinch. I have constructed an example illustrating how you can do this.
Here is the code-behind, which declares a class to represent your items- PickedItem. I then create a collection of these items and populate it with some samples.
public partial class DataBoundCollection : Window
{
public DataBoundCollection()
{
Items = new ObservableCollection<PickedItem>();
Items.Add(new PickedItem("Item 1"));
Items.Add(new PickedItem("Item 2"));
Items.Add(new PickedItem("Item 3"));
InitializeComponent();
}
public ObservableCollection<PickedItem> Items
{
get;
set;
}
}
public class PickedItem
{
public PickedItem(string name)
{
Name = name;
Picked = false;
}
public string Name
{
get;
set;
}
public bool Picked
{
get;
set;
}
}
Now, let's look at the XAML mark-up for this window:
<Window x:Class="TestWpfApplication.DataBoundCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataBoundCollection" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Picked}" Margin="5"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
I create a ListBox to hold the items, and bind its ItemsSource property to the collection I created in the code-behind. Then, I provide the ListBox with an ItemTemplate, which determines how each PickedItem will be rendered. The DataTemplate in this case is as simple as a check-box and some text, both bound to the member variables on PickedItem. Now, when I check any of these items, the data in the underlying collection is modified, in real-time, with no event handlers needed. Ta-da!
alt text http://img405.imageshack.us/img405/1083/databoundcollection.png

Limit the number of rows displayed in Items control

Is there a way to limit the number of rows that get displayed in items control. ?
I have a observable collection of strings which are bound to Items control. I want to limit the number of rows to display to only one. The collection can have more than one.
Thanks,
You can use a IValueConverter for this:
public class ItemsLimiter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int count;
if (Int32.TryParse((string)parameter, out count))
{
return ((IEnumerable<object>)value).Take(count);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
In XAML you can use it like this:
<ItemsControl ItemsSource="{x:Bind Data, Converter={StaticResource ItemsLimiter}, ConverterParameter=12}">
Let's say your ItemsSource is set to MyObservableCollection.
Well, what if you change your ItemsSource so that it is pointing a MyOneItemCollection instead?
Then, just use LINQ to do something like this:
using System.Linq;
MyOneItemCollection = MyObservableCollection.First();
or
using System.Linq;
MyOneItemCollection = MyObservableCollection.Single(item => item.Id = MyId);
If you only ever need one item to display you can show the first item using a ContentControl instead with the same available templating options:
<ContentControl DataContext="{erm:Items FieldName}" Content="{Binding [0]}">
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontSize="14" VerticalAlignment="Center" FontWeight="Bold" />
</DataTemplate>
</ContentControl>
Check out my PaginatedObservableCollection here http://jobijoy.blogspot.com/2008/12/paginated-observablecollection.html
This is a subclassed observableCollection which lets you bind to N items and make the UI display 'n' items when you set ItemsPerPage. In your case if you put 1 and can bind the next and previous also to some buttons as in my sample.
Hope this give you some idea.
You can implement a custom CollectionView that only provides n elements. I did something similar in my autocomplete textbox control implementation, check it out here:
A Reusable WPF Autocomplete TextBox
Scroll down to the header titled Limiting the Completions List see what I did there.
You can do this by creating a CollectionView that produces only a single item. This is quite simple: Just subclass CollectionView and override OnCollectionChanged to set a filter to filter out everything except the first item in the underlying collection:
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
var first = SourceCollection.FirstOrDefault();
Filter = (obj) => obj == first;
}
Now you need to insert this CollectionView into your ItemsSource. If erm:Items's ProvideValue produces the actual collection or a Binding with no Converter, you can just create your own MarkupExtension which either wraps it in your custom view or adds a Converter to do the wrapping. On the other hand, if erm:Items produces a Binding that already has a Converter or you can't rely on knowing what it produces, you should probably use a more general solution - I would suggest attached properties.
To use attached properties, your ItemsControl will be bound like this:
<ItemsControl
my:SinglerCollectionViewCreator.ItemsSource="{erm:Items FieldName}"
... />
and the code in the SinglerCollectionViewCreator class would be:
public class SinglerCollectionViewCreator : DependencyObject
{
public object GetItemsSource(... // use "propa" snippet to fill this in
public void SetItemsSource(....
public static readonly DependencyProperty ItemsSourceProperty = ...
{
PropertyChangedCallback = (obj, e)
{
obj.SetValue(ItemsControl.ItemsSourceProperty,
new SinglerCollectionView(e.NewValue));
}
}
}
The way this work is, whenever your new SinglerCollectionViewCreator.ItemsSource property is set on any object, the value is wrapped inside your SinglerCollectionView class and the ItemsControl.ItemsSource is set on the same object.
Is there a way to do this entirely in XAML, without writing code?
<ItemsControl Height="100" ItemsSource="{erm:Items FieldName}" Grid.Row="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontSize="14" VerticalAlignment="Center" FontWeight="Bold" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have the items source bound like above. The Items markup extension returns observable collection of strings and I have no control over the observable collection.

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