Shortcut to a control in WPF - wpf

I have a TabControl with multiple TabItems. In each TabItem there needs to be this particular UserControl. Right now I'm creating X instances of the control where X is the number of TabItems I have. So this seems like a bad way to do it. So I'm wondering is there a way to have 1 instance of a control, but multiple locations for it. I know that each control can only have one parent so it seems like the answer is no.
[TabItem1]
[CommandLine Control]
[Content unique to TabItem1]
[TabItem2]
[CommandLine Control]
[Content unique to TabItem2]
Is it possible to have one instance of [CommandLine Control] but in these two locations?

If you use a data template for your control and databind the tab control to a collection then the framework will only create one instance of the control and swap out its data context as you change tab items.
You can find a more detailed discussion here: Why do tab controls reuse View instances when changing tab

You can achieve this using triggers which ensure that the control is not in two places at the same time, e.g.
<TabControl>
<TabControl.Resources>
<!-- Define control which is to be reused in resources -->
<TextBox x:Key="SharedTB" Text="test"/>
<!-- This style gets the shared control only if the parent
TabItem is selected -->
<Style x:Key="LoadTBStyle" TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem}, Path=IsSelected}"
Value="True">
<Setter Property="Content" Value="{StaticResource SharedTB}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
<TabItem Header="TabItem 1">
<StackPanel>
<TextBlock Text="Lorem Ipsum"/>
<ContentControl Style="{StaticResource LoadTBStyle}"/>
</StackPanel>
</TabItem>
<TabItem Header="TabItem 2">
<StackPanel>
<TextBlock Text="Dolor Sit"/>
<ContentControl Style="{StaticResource LoadTBStyle}"/>
</StackPanel>
</TabItem>
</TabControl>

Is the control really more or less a shared control? I mean, if you are really wanting a control to be in more places than 1, then you would be better off hosting it outside of the TabItem so that it can be accessed from any of the tab items that need it.
From your example, it looks like you might be best off spawning the items. Personally I would have the TabControls Items property bound to a collection of ViewModels that host a TabItem. Each of those would have a runtime created instance of the UserControl (with associated ViewModel and content).

It sounds like what you are looking to do can be accomplished by using a template.
Look at this link for an example of a tabitem template: http://msdn.microsoft.com/en-us/library/ms752032.aspx

There's no way to have a "shortcut" to it the way you describe that I'm aware of, because your user control can only have one parent.
The only thing that I can think of to do is to shift it manually (via code-behind) when the user clicks on a different tab. But the "stutter" that might cause as you shift it (while better in WPF, I'd imagine than in other frameworks) might be just as bad as whatever the resource usage problems you're seeing by having multiple instances of the user control.
I guess another thing to do would be to cache the creation of the user controls so they're only loaded if the person actually clicks on the tab.

Related

WPF TabControl tabs losing state information

I'm fairly new to WPF and am almost positive I've just overlooked something. I'm having issues preserving the state of user controls within my tabs when I select. It seems very similar to this question but it didn't seem to get resolved.
In my main ViewModel I have an ObservableCollection of the abstract TabableViewModel class. It also implements the INotifyPropertyChanged for the TabControl's SelectedIndex.
I want the TabControl's content to be automatically created so I used a DataTemplate like so:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:EasyViewModel}">
<UserControls:EasyControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ComplicatedViewModel}">
<UserControls:ComplicatedControl/>
</DataTemplate>
<DataTemplate x:Key="TabHeaderTemplate">
<Grid>
<TextBlock Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}" />
<Setter Property="HeaderTemplate" Value="{StaticResource TabHeaderTemplate}" />
<Setter Property="Content" Value="{Binding}" />
</Style>
</Window.Resources>
<TabControl x:Name="contentTab" TabStripPlacement="Bottom"
SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}"
ItemsSource="{Binding TabItems, Mode=TwoWay}"
ItemContainerStyle="{StaticResource TabItemStyle}">
</TabControl>
EasyControl and ComplicatedControl both have a number of text boxes, combo boxes and other fields. Both ViewModels extend the TabableViewModel class and implement INotifyPropertyChanged.
In a typical test the ObservableCollection will contain one instance of EasyViewModel and then two instances of ComplicatedViewModel. The tabs build themselves properly, but the state of the ViewModels is not being maintained.
If I make changes inside EasyControl, switch to the first ComplicatedControl tab and then switch back, all data has been lost.
If I make changes to the first ComplicatedControl and switch to the second, I get those same changes instead of a blank slate. If I then switch to EasyControl and back again, all data is once again cleared.
I've seen quite a few examples where the DataTemplates are basic, but I haven't seen any where UserControls are picked based on ViewModel type. Is there anything special I have to do to maintain state?
I don't want to break the MVVM pattern by creating the UIElements within the ViewModels. I'm also hoping that I don't have to extend any Tab controls to get this working, but I will if I have to. I'm surprised it's this hard to do.
So you have a situation where your UI seems to be losing data. This is generally not possible when using MVVM because your data is in your view model and not the view. Therefore, if it is being reset at any point, then it is your code that is resetting it. You said:
My problem comes from switching between tabs
So at the point when you switch tabs, you must initialise one of your view models and that's why it loses its data. You're the only one that can fix this problem... it is not reproducible from your code example. Search for calls to your view model constructor and you should find your problem.

WPF ComboBox: How to you utilise a generic ItemContainerStyle with binding

I want to utilise a generic style for my ComboBoxItem content and have the text content bound to different properties on my underlying class. So this is the best I can come up with but the bindings are hard coded. So for every class bound to a combobox using this ItemContainerStyle I'd have to implement a "MainText" and "SubText" property.
Question is, is there a way to have the binding soft coded so where the style referenced from a combobox I can specify which string properties of the underlying class are used.
<Style TargetType="{x:Type ComboBoxItem}" x:Key="ComboBoxItemStyleA1">
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="BB" Padding="8,3,8,3" Background="DarkGreen">
<StackPanel Margin="0">
<TextBlock Foreground="White" FontSize="16" Text="{Binding MainText}"/>
<TextBlock Foreground="White" FontSize="8" Text="{Binding SubText}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" TargetName="BB" Value="#FF256294"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And to use the style...
<ComboBox ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource ComboBoxItemStyleA1}" />
Further to dowhilefor's answer (many many thanks - WPF is great but sure is a voyage of discovery)
I used a data template to define the cell look originally - and then wanted to use a comboboxitem based style with a control template defined where I could specify the onmouseover triggers. i.e. these were to change the background color etc.
Butj
a) I couldn't remove the Border section of the template above - the triggers are tied to it by targettype="BB". so I kind of wanted to get the trigger bound to the container such that the datatemplate would pick up the background from the template binding but not sure how to get this plumbed in.
b) I realised that even if I comment out the BB specific bindings on the triggers just to get it to run - the combobox doesn't find and use the DataTemplate I defined. Seems that defining the controltemplate in my comboboxitemstyle stops it picking up the datatemplate.
I hope I make sense here - bottom line is I just want a style that I can apply with triggers in that set the background color of my cobobox item. It should not know what the data is - i.e. be able to plug in a datatemplate that will (template ?) bind to this background color.
Many thanks for the very fast response.
btw I'm using ItemContainerStyle in conjuction with ItemTemplate so I can have a different representation in the dropdown to what appears in the combobox list
First of all don't use the ItemContainerStyle for that. To be more precise never have any Bindings to the datacontext inside an ItemContainerStyle, at least try not. Why? The Style is used for defining the appearance of a combobox item disregarding the content. If you want to define how the content should look like, you use a DataTemplate for that. There are multiple ways to tell the combobox where he can find a proper DataTemplate for the Data you supply. Checkout the property ItemTemplate, ItemTemplateSelector and search for implicit styles, to find out more about them.
So to your problem, create one ItemContainerStyle for you combobox (if you really have to anymore) which doesn't care about the object that will be put into. Now you still need to provide multiple DataTemplates each and everyone with the knowledge of the data object that you want to be templated. There is no way around it, there is no soft databinding. Just try to keep your templates small and simple. If for some reason you need the exact same template, but your properties are just named differently, why not use a wrapper item for the DataContext with the properties Caption, Description and you can decide in code how these properties are filled with your real data wrapped into this object.

Difference between Label and TextBlock

According to the Windows Applications Development with Microsoft .NET 4 70-511 Training Kit
What is the difference between the Label control and TextBlock control since both are content controls and just displaying text?
TextBlock is not a control
Even though TextBlock lives in the System.Windows.Controls namespace, it is not a control. It derives directly from FrameworkElement. Label, on the other hand, derives from ContentControl. This means that Label can:
Be given a custom control template (via the Template property).
Display data other than just a string (via the Content property).
Apply a DataTemplate to its content (via the ContentTemplate property).
Do whatever else a ContentControl can do that a FrameworkElement cannot.
Label text is grayed out when disabled
Label supports access keys
Label is much heavier than TextBlock
Source
Some more interesting reads below
http://www.wpfwiki.com/WPF%20Q4.1.ashx
What is the difference between the WPF TextBlock element and Label control?
Labels usually support single line text output while the TextBlock is intended for multiline text display.
For example in wpf TextBlock has a property TextWrapping which enables multiline input; Label does not have this.
Label is ContentControl which means that you can set anything as a content for it. Absolutely anything including strings, numbers, dates, other controls, images, shapes, etc. TextBlock can handle only strings.
Although TextBlock and Label are both used to display text, they are quite different under the covers.
=> Label inherits from ContentControl, a base class that
enables the display of almost any UI imaginable.
=> TextBlock, on the other hand, inherits directly from FrameworkElement, thus missing out on the behavior that is common to all elements inheriting from Control.
The shallow inheritance hierarchy of TextBlock makes the control lighter weight than Label and better suited for simpler, noninteractive scenarios.
PS: However, if you want access keys to work or want a more flexible or graphical design, you’ll need to use Label.
Probably the most annoying feature of TextBlock is the implicit style lookup behavior, which is scoped to only to the closest DataTemplate. This is a default behavior for non Control xaml elements.
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
</Style>
<Style TargetType="Label">
<Setter Property="Foreground" Value="Red"/>
</Style>
</StackPanel.Resources>
<ContentControl Content="Test">
<ContentControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
<ContentControl Content="Test">
<ContentControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding}"/>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</StackPanel>
Yields a result of:
You can read more about it here.

Puzzle - Dynamically change data template control from another data template

I have a DataTemplate that contains an Expander with a border in the header. I want the header border to have round corners when collapsed and straight bottom corners when expanded. What would best practice be for achieving this (bonus points for code samples as I am new to XAML)?
This is the template that holds the expander:
<DataTemplate x:Key="A">
<StackPanel>
<Expander Name="ProjectExpander" Header="{Binding .}" HeaderTemplate="{StaticResource B}" >
<StackPanel>
<Border CornerRadius="0,0,2,2">
This is the expander datatemplate:
<DataTemplate x:Key="B">
<Border x:Name="ProjectExpanderHeader"
CornerRadius="{Binding local:ItemUserControl.ProjectHeaderBorderRadius, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}}"
Background="{StaticResource ItemGradient}"
HorizontalAlignment="{Binding HorizontalAlignment,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}},
Mode=OneWayToSource}">
<local:ItemContentsUserControl Height="30"/>
</Border>
</DataTemplate>
Bind the CornerRadius property to the Expander.IsExpanded property and attach an IValueConverter that returns rounded corners when false and straight bottom corners when true. It's not the most elegant, but it will get the job done.
The other way to do this, if using MVVM, would be to expose a boolean property and bind it to the Expander.IsExpanded property. Then expose another property for the CornerRadius, which checks the boolean property and returns the appropriate values. This is definitely the "best practice" way to go about this.
Another way to do this is by editing the control template. The argument can be made that this is the best practice, though I'm not sure I'm ready to commit to that.
It's straightforward to do this if you have Expression Blend. An advantage of editing the control template is that it separates the behavior of the Expander from your data template, so that you can reuse it across different types of data. A disadvantage is that you end up embedding the properties of the header's Border in the control template, so you can't really change them for any individual instance of the control. (Other disadvantages: you have to have Expression Blend, and it produces a big bolus of XAML that you have to put in your resource dictionary.)
In Expression Blend, create an empty page and put an Expander on it. Right-click on the Expander and pick "Edit Template/Edit a Copy...". Give it a name like "ExpanderRoundedCorners".
This will add about 200 lines of XAML to Page.Resources, but most of this is used to create the graphics on the expand button. Switch to XAML view and search for the ToggleButton named "HeaderSite". This is the expand button. Note that its Content property is set to {TemplateBinding Header}. We'll want to fix that.
Delete the Content property, and add a child element to the ToggleButton like this:
<ToggleButton.Content>
<Border x:Name="HeaderBorder" BorderBrush="Red" BorderThickness="2">
<ContentPresenter Content="{TemplateBinding Header}"/>
</Border>
</ToggleButton.Content>
Now find the trigger that makes ExpandSite visible when the ToggleButton is pressed. Add this setter to it:
<Setter TargetName="HeaderBorder" Property="CornerRadius" Value="4"/>
That's it. Now every time you create an Expander with the ExpanderRoundedCorners style, the header content will be enclosed in a Border whose corners are rounded when the Expander is expanded.
You'll probably want to jigger around with this a little more when you've got it working. At the least, you'll want to remove the Border from the header template in your style, since it's now part of the control template.

WPF Style Active Item

I am attempting to create a reusable navigation style Custom Control in WPF, like a navigation bar on a website. It will contain links to all the main Pages in my app. This control can go on top of all my Pages in my NavigationWindow. Giving a nice consistent look and feel across pages, like a website.
My issue is in styling the current page's link differently than the other pages' links, so that you can quickly glance at it and see which page you're on. Since the control is the same on each Page, I need to tell it which page is "active" and have it style that link appropriately.
My first thought was to simply place Is<Page>Active properties on the control, one for each page, and then set the appropriate property to true on the page. (Or I could use one property that accepts an Enum value instead of having many properties, either way)
Example:
<local:Header IsHomePageActive="True" />
Now in the control template for my Header Custom Control, I can create a DataTrigger that watches this property:
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource FindAncestor, AncestorType={x:Type local:Header}}, Path=IsHomePageActive}" Value="true">
<Setter ... />
<Setter ... />
<Setter ... />
</DataTrigger>
</Style>
After all that background, here's my question. This works, but I'm going to have to duplicate that DataTrigger, and all the Setters in it, for every single Page I have, because the Trigger has to directly reference the "IsHomePageActive" property which only applies to the one link. So I need a different Style for every link, even though the actual STYLE its describing is exactly the same (by which I mean, the Setters are the same). The only difference is what property the trigger is watching.
Is there some way to do this (or something with the same end result) without ending up with hundreds of lines of duplicated XAML?
How about using a master/detail pattern () with a listbox(say) as the master and your pages as the details.
Then specify your style for the selected item in the list.
The page will change when a different list item is selected and it will look different to the other items.
if you have a dependency property such as List Pages where Page inherits from UserControl and has a string Title you can use
<ListBox
ItemsSource="{Binding Pages}"
IsSynchronizedWithCurrentItem="true">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ScrollViewer VerticalScrollBarVisibility="Auto"
Content="{Binding Pages/}" />
Then just set style for the selected item on the listbox

Resources