Enable a TabItem via Binding - wpf

I want to use MVVM in an application where the different pages are TabItems.
For this I use an observable collection of my view models (Items) and bind it to the tabcontrols ItemSource.
For each view model, I created an individual data template to render the correct view like this:
<DataTemplate DataType="{x:Type baseVm:AViewModel}">
<baseVw:AView />
</DataTemplate>
To display the correct name in the tab's header I created another data template to be applied to each of the tab control's elements:
<DataTemplate x:Key="ViewModelTabTemplate">
<DockPanel>
<ContentPresenter Content="{Binding Path=Name}"/>
</DockPanel>
</DataTemplate>
The tab control looks like this:
<TabControl x:Name="myTabControl"
ItemsSource="{Binding Items}"
ItemTemplate="{DynamicResource ViewModelTabTemplate}">
</TabControl>
What I want to do now is to enable/disable tabs from within the view model that contains the collection. The view model's base class contains a dependency property IsEnabled and I would like to bind this to the views. If I do this directly in the view like this:
IsEnabled="{Binding IsEnabled, FallbackValue=true}"
the tab page's content gets disabled when I turn the IsEnabled property to false. But what I really want is to also disable the tabpage's tab and not just the content.
Thanks for any help!

Maybe you could try something like this -
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

Related

Binding TabControl.ItemsSource to ViewModels whose View's are a TabItem

I need some TabItems to have a customized Header.
For example, given the following (working) XAML:
<TabControl>
<TabItem>
<TabItem.Header>
<Button>Header 1</Button>
</TabItem.Header>
<Label>Content 1</Label>
</TabItem>
<TabItem>
<TabItem.Header>
<Label>Header 2</Label>
</TabItem.Header>
<Grid>
<TextBlock>Content 2</TextBlock>
</Grid>
</TabItem>
</TabControl>
I would like to extract the tab items into their own Views + ViewModels. The TabItem's View should still be a TabItem, so that I can configure the Header per tab item instead of setting TabControl.ItemTemplate and using a DataTemplateSelector to achieve different headers per tab item.
At the same time I'd need to be able to bind the selected tab item view model to a property ActiveItem. => The underlying view-model for the TabControl is a Conductor.Collection.OneActive<T> (only the selected tab should be activated).
If there's an alternative to using TabItem as view-type, but still achieving the Header and Content to be specified in the same view, it would be acceptable, too.
You should be able to achieve this by binding a TabControlViewModel to the TabControl, and that VM should have an ObservableCollection of TabViewModels (maybe a base class or interface). You would bind your collection of TabViewModels to the TabControl's ItemsSource. Here is my implementation, but using the Telerik TabControl (should be same for MS):
<telerik:RadTabControl x:Name="RadTabControl"
Grid.Row="0"
Align="Justify"
ContentTemplateSelector="{StaticResource LoggerDataTemplateSelector}"
IsContentPreserved="True"
IsDefaultItemSelected="True"
ItemsSource="{Binding LogHistory}"
SupressSelectedContentTemplateReapplying="False">
<telerik:RadTabControl.ItemContainerStyle>
<!-- Allow IsSelected to be bound to view models-->
<Style BasedOn="{StaticResource RadTabItemStyle}"
TargetType="{x:Type telerik:RadTabItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</telerik:RadTabControl.ItemContainerStyle>
<telerik:RadTabControl.ItemTemplate>
<!-- Define what is shown in the header -->
<DataTemplate>
<Grid Height="30">
<TextBlock VerticalAlignment="Center"
Text="{Binding Title}" />
</Grid>
</DataTemplate>
</telerik:RadTabControl.ItemTemplate>
</telerik:RadTabControl>
Be aware, MS doesn't have something like IsContentPreserved, so switching tabs that have a lot of data to show will be rather timely. There are also a couple other properties not in MS TabControl, however the important properties should all be there. In this case, your TabViewModel should have a IsSelected property and Title property.

How to retrieve VisualChild from ControlTemplate

I have a WPF application in .NET 3.5 SP1 which is using TabControl.
In that we have TabItems, which in turn have their Styles to determine currently displayed items.
Let's say we have a TabItem named Books, now Books will have three stages of display:
1. Loading results,
2. Displaying results,
3. Displaying no results - i.e. nothing found.
<TabControl>
<TabItem Header="Books"/>
<TabItem Header="DVD's"/>
...
</TavControl>
Now I have 5 TabItems which let's say represent "DVD's", "Blu-Rays", "CD's", "Books" and "Comics".
<TabItem Header="Books">
<Control>
<Control.Resources>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<ListView ItemsSource="{Binding Books}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<!-- Assign different Visuals depending on the current state of the app, i.e. Loading, No results, results found
<DataTrigger .../>
</Style.Triggers>
</Style>
</Control.Resources>
</Control>
</TabItem>
Underneath the TabItem I have a TextBlock to display number of currently found results:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="Displaying {0} records for your Shop ({1})" Converter="{StaticResource tstMVC}">
<Binding ElementName="Tc" Path="SelectedValue"/>
<Binding Path="ShopId" FallbackValue="Liverpool"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
Converter is there for me to check what values are passed in the MultiBinding.
ISSUE:
When user selects a tab item I would like to display current number of the items displayed but I can't locate the ListView in the Control, as that's the Current Content of the TabItem.
I have tried TabControl.SelectedItem, SelectedValue and still can't find Current ItemsSource.Count.
Thanks in advance
UPDATE:
I have tried both of the solutions big thanks to #Sheridan and #pushpraj!
Unfortunately I haven't used either of them, instead I used ListView inside of the TabItem and then accessed it with this code:
<TabControl Name="Tc">
<TabItem><ListView ItemsSource="{Binding Books}"/></TabItem>
...
</TabControl>
<TextBlock Text="{Binding ElementName=Tc, Path=SelectedItem.Content.Items.Count}"/>
This way Content of my TextBlock changes every time user selects different Tab.
P.S. Nevertheless I wouldn't have done it without evaluation of both answers.
if you have separate collection classes for all you entities you may use the following approach
define data templates for your collection classes
eg
<DataTemplate DataType="{x:Type l:BookCollection}">
<ListView ItemsSource="{Binding}" />
</DataTemplate>
xaml
<TabControl x:Name="Tc">
<TabItem Header="Books"
Content="{Binding Books}" />
<TabItem Header="DVD's"
Content="{Binding DVDs}" />
</TabControl>
or if you do not have separate collections then use DataTemplate as follows
<TabControl x:Name="Tc">
<TabControl.ItemTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabItem Header="Books"
Content="{Binding Books}" />
<TabItem Header="DVD's"
Content="{Binding DVDs}" />
</TabControl>
then the binding to get the selected tab's items count will be
<TextBlock Text="{Binding SelectedItem.Content.Count, ElementName=Tc}" />
In WPF, we generally work with data elements rather than UI elements. By that, I mean that it is customary to data bind data elements from our code behind or view models to the UI elements in the views, UserControls and Windows.
Therefore, if you have a data collection property named Books in your code behind or view model, then you can simply refer to that collection to find out how many items are in it, rather than trying to find it via the UI controls:
<ListView ItemsSource="{Binding Books}" />
You could even expose the count of items as a separate property and data bind to it directly:
public ObservableCollection<Book> Books
{
get { return books; }
set
{
books = value;
NotifyPropertyChanged("Books");
NotifyPropertyChanged("BookCount");
}
}
public int BookCount
{
get { return Books.Count; }
}
UPDATE >>>
In response to your latest comment, you can find out how to access UI elements from within a ControlTemplate from the How to: Find ControlTemplate-Generated Elements page on MSDN. In short though, you need to access the element that has the ControlTemplate applied (the relevant TabItem in your case) and then you can use the FrameworkTemplate.FindName Method to find the internally declared elements. Take this example from the linked page:
// Finding the grid that is generated by the ControlTemplate of the Button
Grid gridInTemplate = (Grid)myButton1.Template.FindName("grid", myButton1);
// Do something to the ControlTemplate-generated grid
MessageBox.Show("The actual width of the grid in the ControlTemplate: "
+ gridInTemplate.GetValue(Grid.ActualWidthProperty).ToString());

Get context in which a view is rendered?

In my WPF application I have a viewmodel class called CompanyViewModel.
Sometimes, an instance of this class is set as the DataContext of my main window, which is defined like this:
<window x:Class= ..... >
<Grid>
<ContentControl Content="{Binding }"></ContentControl>
</Grid>
</Window>
In this case I want a view to be used that displays all the properties of the viewmodel.
Other times, a ListView control has its itemsource set as a collection containing instances of CompanyViewModel. Here, I want a view to be used that renders only some important properties.
I have this in the resource dictionary of MainWindow.xaml:
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView></vw:CompanyView>
</DataTemplate>
Is it possible to select a view for the viewmodel based on the context where the viewmodel is bound? For instance, to use CompanyView when displayed in the ContentControl of a window or when in a TabControl, and to use CompanyViewSmall where displayed in a ListView?
The DataTemplate to use is first looked for locally, and then looked for further up the Visual Tree hierarchy if it's not found.
Because of this, you can specify the DataTemplate to use further down the hierarchy to use something different than normal.
For example, the following will use the CompanyView anywhere the CompanyViewModel is in the visual tree, except in the specific ListView where the DataTemplate is specified as the smaller view.
<Window.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyView />
</DataTemplate>
</Window.Resources>
<ListView>
<ListView.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</ListView.Resources>
</ListView>
You could also use an implicit style for the ListView telling it to use the smaller template in the .Resources, however this will apply the smaller view to any ListView, not just specific ones, and if you ever apply another style to a ListView you'll have to remember to inherit the default style to keep the smaller DataTemplate.
<Style TargetType="{x:Type ListView}">
<Style.Resources>
<DataTemplate DataType="{x:Type vm:CompanyViewModel}">
<vw:CompanyViewSmall />
</DataTemplate>
</Style.Resources>
</Style>

How to bind to Tabcontrol.Items

I have a WPF application that I'm trying to dynamically add items to a tabcontrol. I have a list of menu items that should be databound to the tabcontrol's items. The only problem is that TabControl.Items does not notify others that items have been added. I've tested this by binding instead to TabControl.Items.Count and get calls to the converter (but the value passed in is the count and not something useful). Here's the relevent code that doesn't get databound properly because Items doesn't call out updates:
<MenuItem ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}, Path=Items, Converter={StaticResource TabControlItemConverter}}">
This MenuItem XAML is inside a ControlTemplate for a TabControl. With static items, i.e., items that are already defined in a TabControl, this code works perfectly. But I have a TabControl that gets items added at runtime and can't seem to update this binding. Has anyone added some sort of attached property to a TabControl that can bind to the Items collection?
Edit for background info
The TabControl that has items added to it is a region (this is a Prism application). Here is the relevent XAML
<TabControl cal:RegionManager.RegionName="{x:Static local:LocalRegionNames.SelectedItemRegion}" >
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource TabItemStyle}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Style="{StaticResource tabItemImage}" Height="20" />
<TextBlock Text="{Binding Content.DataContext.TabHeader, RelativeSource={RelativeSource AncestorType=TabItem}}" VerticalAlignment="Center" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The relevent code for adding a view to the region is here:
ProjectDetailView view = new ProjectDetailView();
ProjectDetailViewModel viewModel = new ProjectDetailViewModel();
viewModel.CurrentProject = project;
view.DataContext = viewModel;
IRegionManager retManager = RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Add(view, null, true);
RegionManager.Regions[LocalRegionNames.SelectedItemRegion].Activate(view);
All this works fine...views get added, the tab control adds items, and views appear. But the Items property on the tabcontrol never broadcasts the changes to its collection.
You do the same thing for TabControls, you bind the ItemsSource, the only thing you need to take into account is that the source collection should implement INotifyCollectionChanged if you want it updated if items are added. ObservableCollection<T> already implements the interface and is often used as source for such bindings.

Exposing a sub-control in a UserControl for use in XAML

I have a UserControl that contains a TreeView. I want the user to be able to set the properties of the inner TreeView control via XAML and I'm not sure how to do that.
I've tried creating a public property on the UserControl to the TreeView, but that only allows me to set a SelectedItemChanged trigger.
I'd like to do something like:
<ExampleUserControl>
<ExampleUserControl.TreeView.ItemTemplate>
...
</ExampleUserControl.TreeView.ItemTemplate>
</ExampleUserControl>
Or:
<ExampleUserControl TreeView.ItemsSource="{Binding Foo}" />
I would prefer not to create properties in the UserControl for each TreeView property, and I don't want to force the user to define the control in C#.
As for passing multiple properties to the child control in your user control, you can always expose a Style property.
ie ChildStyle
For the ItemsSource unless you use [Josh Smith's Element Spy / Data Context Spy / Freezable][1] trick, you will have a disconnect on DataContexts.
So either you employ those tricks or simply have 2 properties.
1) the ItemsSource
2) the ChildStyle
The xaml ends up...
<ChildTreeAnswer:MyControl ItemsSource="{Binding Items}">
<ChildTreeAnswer:MyControl.ChildStyle>
<Style>
<Setter Property="ItemsControl.ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="5">
<TextBlock Text="{Binding }" />
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ChildTreeAnswer:MyControl.ChildStyle>
</ChildTreeAnswer:MyControl>
Then in your user control do... (I used a listbox for simplicity sake)
<ListBox ItemsSource="{Binding ItemsSource}"
Style="{Binding ChildStyle}" />

Resources