WPF TabControl tabs losing state information - wpf

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.

Related

How to support multiple views with the same viewmodel?

What is the best way of using the same viewmodel to support multiple views?
(Yes..I have read "MVVM (with WPF)-Binding Multiple Views to the SameViewModel", but if I read it correctly, the answer involved creating different viewmodels which is not what I want to do here).
In the below code the StringViewModel supports the InkStringView when the InkStringView consists of defined rows. I wish now to define a second view that consists of columns but must keep the same datacontext of StringViewModel. In the second view, the controls have different positions and sizes that the StringViewModel calculates but thier function and purpose remain the same. It would be ideal if the module that creates the StringViewModels in Strings, an observablecollection, does not know what view will be used leaving the final decision to the xaml of the usercontrol.
My question is how to design either the StringViewModel and/or the DataTemplate to allow for different views and calculations based on that view by changing only the DataTemplate.
(I tried inheriting the StringViewModel to different viewmodels, each viewmodel specific to its view, but it did not work).
Thanks in advance for any help or suggestions. Or is their a better way?
Example:
<DataTemplate DataType="{x:Type vm:StringViewModel}">
<v:InkStringView_2 /> <----CHANGING THE VIEW TO COLUMNS. ViewModel needs
</DataTemplate> to perform calculations specific to the view.
The usercontrol is:
<UserControl.Resources>
<DataTemplate DataType="{x:Type vm:StringViewModel}">
<v:InkStringView />
</DataTemplate>
</UserControl.Resources>
<Grid>
<ItemsControl
ItemsSource="{Binding Strings}" >
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Margin" Value="{Binding Margin}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Grid>
</UserControl>
just a suggestion:
you have a viewmodel MyViewmodel which you wanna display with different DataTemplates. then for me a easy way would be to create "new" Classes with "no" implementation
public class MyT1 : MyViewmodel {}
public class MyT2 : MyViewmodel {}
public class MyT3 : MyViewmodel {}
so now all MyT1, MyT2, MyT3 have the same methods and stuff, but you can create a datatemplate foreach of them
<DataTemplate DataType="{x:Type vm:MyT1}">
<v:T1View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyT2}">
<v:T2View />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:MyT3}">
<v:T3View />
</DataTemplate>
or you dont go the Viewmodel First approach and do View First and then you can choose the view you want with your Datacontext
I've had a similar problem, the way I've tackled it is by using ControlTemplate so:
In your View:
<Control>
<Control.Resources>
<Style TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Control">
<!-- Here you put you view it could be a UC if you want -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style
</Control.Resources>
</Control>
Then in your Style.Resources you can define Triggers or DataTriggers to change the Template property of your Control. This way you keep the ViewModel as it is and you just change the Views that are getting the data. Hope this makes sense, if anything give us a shout and I'll put more info in.
HTH
Not an answer, but a simple solution.
Redefine Strings, the ItemsSource, as
ObservableCollection<StringViewModelBase> Strings
then create children of StringViewModelBase as
public class StringByRowViewModel : StringViewModelBase
then change the DataTemplate in the resources to be
<DataTemplate DataType="{x:Type vm:StringByRowViewModel}">
<v:InkStringByRowView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:StringByColumnViewModel}" >
<v:InkStringByColumnView />
</DataTemplate>
and lastly, I see no choice but to change the module that builds the Strings (at least as far as the viewmodel goes). Instead of
StringViewModelBase svm = new StringViewModelBase(text,color, w);
use
StringByColumnViewModel svm = new StringByColumnViewModel(text,color,w);
with
Strings.Add(svm);
Apparently, the ObservableCollection only needs a common parent and the Data Type is still preservered in the XAML.

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.

Shortcut to a control in 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.

Silverlight databinding to itemsource in parent's parent datacontext

I have a datagrid where in one of the column's header I would like to have a dropdown that filters the data in the grid. The issue being that the datacontext that has the values that would be in this dropdown is in the usercontrol's viewmodel not the datagrids itemssource so the list doesn't seem to be available to the dropdown.
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assignee" />
<ComboBox x:Name="cboAttorneyHdr" ItemsSource="{Binding Path=Attorneys}"
Margin="3,0,0,0" SelectedItem="{Binding Path=SelectedAttorney, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTemplateColumn.HeaderStyle>
I found an example using relative source for WPF that seems to be asking the same thing but it seems that this doesn't work for Silverlight. I have tried setting this manually in the code behind but the combobox does appear to be available there either!
One way I've found around this problem is to use some helpers as detailed here - it's just one of the possible implementations, but it amounts to emulating the WPF RelativeSourceBinding with AncestorLevel/AncestorType which is still not available in SL4. Or you could try to google 'silverlight combobox in datagrid' for more ways to solve it, I'm sure you can imagine it's a pretty common problem :)
I found this solution which actually ended up working great though it's going to take me a bit to actually understand what the heck it's really doing.
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx

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