I have looked at most resources but i can find a good solution. I have a tab control HARD coded.
<TabControl TabStripPlacement="Left" Padding="0" Style="{DynamicResource SettingsTab}" ItemContainerStyle="{DynamicResource SettingsTabItemStyle}" Background="WhiteSmoke" >
<TabItem Header="ΓΕΝΙΚΑ" Margin="0" IsEnabled="False" > <Grid /></TabItem>
<TabItem Header="Προσωπικό" Margin="0" IsSelected="True">
<Grid MinHeight="400">
<ContentControl HorizontalAlignment="Stretch" Margin="50,67,50,0" Name="ActiveItem" />
</Grid>
</TabItem>
<TabItem Header="Τραπέζια" Margin="0">
<Grid />
</TabItem>
UPDATE - Restate problem
Here is my customized tab control. The gray text is a disabled tab item it acts like a group. Like General settings, System settings etc. So it has a role as a navigation menu. Curently i have a content control at each tabitem (not the disabled ones) and bind the view model i want.
But i cant use Conductor.Collection.OneActive with CM.
Why?
I have seen helloscreens example from CM sample and other samples but the problem here is that if i do this via binding then there is no way to display the disabled tabitems other than creating a dummy view model that serves no purpose.So how can i achieve this ?
Update: source here
I have a tab control HARD coded.
Is this a requirement?
But i cant use Conductor.Collection.OneActive with CM. Why?
I think the reason this won't work is to use Conductor.Collection.OneActive you need to bind the ItemsSource to the Items collection. If you're going to bind ItemsSource you can't also describe the tab items in XAML.
I created a solution that doesn't involve describing the tab items in xaml.
The key parts:
In ShellView the TabControl ItemContainerStyle is described to the tab items IsEnabled property can be bound to the view model.
<TabControl x:Name="Items"
Grid.Row="1"
TabStripPlacement="Left">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="IsEnabled" Value="{Binding IsEnabled}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
ShellViewModel is similar but we're now activating the second view model because the first one is not enabled.
public class HeaderViewModel : BaseTabViewModel
{
public HeaderViewModel(string name)
{
DisplayName = name;
IsEnabled = false;
}
}
public ShellViewModel(Tab2ViewModel tab2ViewModel,
Tab3ViewModel tab3ViewModel
)
{
Items.Add (new HeaderViewModel ("ΓΕΝΙΚΑ"));
Items.Add(tab2ViewModel);
Items.Add(tab3ViewModel);
ActivateItem (tab2ViewModel);
}
Enable or disable the tab item in the constructor of the view model.
public Tab2ViewModel()
{
DisplayName = "Προσωπικό";
IsEnabled = true;
}
The un-styled result is that the first tab item is disabled and the next two are enabled.
Related
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.
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());
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>
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.
I've been trying to create a custom skin/template for a TabControl in WPF.
I want the tabs to be displayed in a ComboBox. When you select the item from the ComboBox, I want the content area of the tab control to display the TabItem contents.
Here's an image showing what I'm looking for:
I could do this using some sort of master-detail setup with data objects and templates, but the problem is I want to set up the controls using the TabControl XAML format, like this:
<TabControl Style="{DynamicResource ComboTabControlStyle}">
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</TabControl>
Any thoughts or suggestions?
It is very easy to change the layout of the tab items using a different Panel, but a ComboBox is an ItemsControl, not a Panel.
I tried putting the ComboBox in the TabControl template and binding the ItemsSource of the ComboBox to the TabControl.Items property, but it didn't seem to work correctly.
I also tried creating a custom panel that only shows one "selected" item at a time and shows all of the items in a drop-down when you click on it (basically a "ComboBox" panel). I ran into trouble because visuals can only be in one place in the visual tree. So putting the children of the panel into a popup caused an exception to be thrown.
Anybody have any other ideas?
Thanks for the help!
It's surprisingly difficult to do what you're trying to do. This comes very close:
<DockPanel>
<ComboBox x:Name="ItemSelector" DockPanel.Dock="Top">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type TabItem}">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<TabItem Header="TabItem1">
<TextBlock Text="TabItem1 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem2">
<TextBlock Text="TabItem2 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
<TabItem Header="TabItem3">
<TextBlock Text="TabItem3 Content!" FontSize="18.667" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</TabItem>
</ComboBox>
<ContentPresenter Content="{Binding SelectedItem.Content, ElementName=ItemSelector}" DockPanel.Dock="Top"/>
<TextBlock/>
</DockPanel>
Where it breaks down, oddly, is displaying the selected item in the ComboBox: the ItemTemplate is ignored when rendering the item, so the selection box contains a TabItem. To fix this, you have to subclass ComboBox and implement a read/write SelectionBoxItemTemplate dependency property, because, for some reason that I'm sure is not as stupid as it seems to me at this moment, that property's read-only.
Create a new class, inherit from panel, put a combo box inside, do a lot of parent binding, and use it. it'll do the trick.
You must use custom class to write the tabs as they are regular tab items.
I found a solution.
I created a custom Control class (MasterDetailControl).
This class has two template parts:
[TemplatePart(Name = "PART_MasterSelector", Type = typeof(Selector))]
[TemplatePart(Name = "PART_DetailPresenter", Type = typeof(ContentPresenter))]
The control has an items dependency property:
public IList Items { ... }
I added a helper class:
[ContentProperty("Detail")]
public class MasterDetail
{
public object Master { get; set; }
public object Detail { get; set; }
}
Items that are placed in the Items DP are processed by the MasterDetailControl. If they are of type MasterDetail, the master is added to the selector items list. For other child item types, a new MasterDetail object is created with the object assigned to the master and detail fields. A separate list maintains all of the generated MasterDetail objects with indexes that correspond to those in the Selector control.
When the SelectionChanged event fires on the Selector object, I set the ContentPresenter's Content property to the Detail field of the item corresponding to the selected master object.
If anyone wants more details, feel free to comment.
In the end, I can now use this control with a simple control template specifying any selector object (ListBox, ComboBox, etc) and a ContentPresenter.