Switching view/view model based on a parameter - wpf

In a wpf application I've to show different filter user control based on a parameter passed to the main view model. Each filter's view model implements IReportFilter and the main view model has a property of type
IReportFilter Filters {get;set;}
How do I resolve the correct xaml view?
Thanks in advance

Assuming that you have a base class instead of IReportFilter,say ReportFilter.
Follow the below steps:
1. Define DataTemplate For Filter1VM & Filter2VM & set the usercontrols.
Note that I have naming conventions while defining the control & VMs.
<DataTemplate DataType={x:type viewModels:Filter1VM}>
<usercontrols:Filter1/>
</DataTemplate>
<DataTemplate DataType={x:type viewModels:Filter2VM}>
<usercontrols:Filter2/>
</DataTemplate>
2. You need to define a custom DataTemplateSelector.
class CustomDataTemplateSelector:DataTemplateSelector
{
public ovverride DataTemplate SelectTemplate(object item,....)
{
Type t=item.GetType();
string typename=t.Name;
string viewName=typeName.Replace("VM",String.Empty);
DataTemplate dt=App.Current.Resources[viewname] as DataTemplate;
return dt;
}
}
3.Define a property TemplateSelector in ReportFilter class & initialise it in constructor as:
TemplateSelector=new CustomDataTemplateSelector();
4. In your window's VM, create ReportFilter Filter property:
ReportFilter Filter {get;set;}
In your application Window,add ContentControl where you need to place the filterControl:
ContentControl Content="{Binding Filter}"
ContentTemplateSelector="{Binding Filter.TemplateSelector}"
In your window's view model, assing Filter as Filter1VM/Filter2VM based on the parameter passed.

Try the following
<ContentControl Content="{Binding SomeContent}"/>
Then in viewmodel create two controls for your needs and simply switch them based on your filter
UPDATE
If you don't want to create controls in ViewModel then you can do the following for example:
<Grid>
<Column / Rows Definitions>
<YourControl1 Grid.Row="X" Grid.Column="Y" IsVisible={Binding BoolFilter1, Converter={StaticResource Bool2Visibility}/>
<YourControl1 Grid.Row="X" Grid.Column="Y" IsVisible={Binding BoolFilter2, Converter={StaticResource Bool2Visibility}/>
....
</Grid>

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

Adding controls dynamically according to data type WPF

I need to create a childwindow that should contain the controls according to the model passed to it. For Example, If a model contains 5 properties (it can contain any number of properties), 2 of type string, 1 datetime and 2 lists, it should create 2 textboxes with labels as property name, 1 Datepicker with lable as property name, and 2 comboboxes with label as property name. Basically, controls should be created dynamically according to properties along with the Label as name of the property. I am following MVVM. Any help will be appreciated. Thanks.
Get the list of PropertyInfos of your model and wrap them in ViewModels. Then use DataTemplates with implicit keys to generate your controls.
Step1: Get PropertyInfoViewModels
var vms = model.GetType().GetAllProperties.Select(p=> ViewModelFactory.Create(p));
Your factory should return a StringPropertyViewModel for string properties, etc.
abstract class PropertyViewModel<T> : INotifyPropertyChanged
{
public string Caption {get; set;}
public T Value {get; set;}
}
Step2: DataTemplates
<DataTemplate TargetType="{x:Type sys:StringPropertyViewModel}">
<StackPanel Orientation="Horizontal">
<Label Text="{Binding Caption}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
Step3:
Ensure the DataTemplates are in the Resources section of your Window or can be resolved via a ResourceDictionary.
Step4:
In the window ViewModel, expose the generated PropertyViewModels
<Window...>
...
<ItemsControl ItemsSource="{Binding ModelPropertyViewModels}">
<ItemsControl.Resources>
<!-- This might be a good place to post your DataTemplates --->
<ItemsControl.Resources>
</ItemsControl>
...
</Window>

View inside a View and data synchronization

I am having one view where I need to display some Grid and TabControl. There is one column on a grid that should display something like a Note (Remark) property. Since this field can contain large amount of data, I am going to have one tab with TextBox control that should allow user to see/edit note, while grid column will show only a few first letters on the note.
I am going to post relevant parts only:
public classSomeViewModel : ViewModelBase
{
public SomeViewModel()
{
TabScreens = New List<ViewModelBase>();
TabScreens.Add(new AnotherViewModel1());
TabScreens.Add(new AnotherViewModel2());
}
List<ViewModelBase> TabScreens{get;set;}
}
SomeView xaml:
<DataTemplate DataType="{x:Type vm:AnotherViewModel1}">
<vw:AnotherView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AnotherViewModel2}">
<vw:AnotherView2 />
</DataTemplate>
AnotherView2:
<Grid>
<TextBox Text={Binding Note} />
</Grid>
AnotherViewModel2:
public class AnotherViewModel2
{
public string Note {get;set;}
}
}
So TabControl on View is bound to TabScreens. DataTemplates ensure that both AnotherView1 and AnotherView2 will be loaded when SomeView is loaded. Each row in grid contains different remark. What is the cleanest way to synchronize SomeViewModel Remark and AnotherViewModel2 Remark?
Combine the 2 view models into a master view model with a single Remark property. Don't introduce a need to synchronize if it's not necessary.

WPF listview display converter

I have a collection of objects that derive from a Person class and I want to bind this collection to the ItemsSource of a ListView.
I want to specify a string to display in the ListView Items. This string will be a composite of properties found on the derived classes.
I also want to bind the SelectedItem of the ListView to a property of type Person in my view model.
As far as I see it I need a string converter for my display string but I'm unsure how to bind to the items within the ItemsSource to generate the composite display string.
Can anyone give me a pointer?
Thanks.
You can either overwrite the ToString() method of your derived classes to return your composite display string, or you can create a Converter like you are suggesting and pass it the entire Item. The converter would then check that the item is of a specified type, and if so compose a string of whatever properties you want.
you dont need the StringConverter, you need DataTemplate
using DataTemplate, you can choose how you would like to display you data as an item in your listBox.
If you could consider your derived class a ViewModel then you could just add a property to that class and then display it in the ListView ItemTemplate. Or like Rachel suggested override your ToString Method and then in your display binding simply write "{Binding}" which will force WPF to call the ToString method
e.g.
public class DerivedPerson : Person
{
public string DisplayString
{
get
{
return string.Format("{0} {1}",FirstName,LastName);
}
}
}
And you xaml:
<ListView ItemsSource="{Binding PersonList}" SelectedItem="{Binding SelectedPerson}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text={Binding DisplayString}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Rendering a non UIElement via binding

If I have an object derived from System.Windows.DispatcherObject but defines a ControlTemplate.
public class A : System.Windows.DependencyObject
{
public ControlTemplate ControlTemplate {get; set;}
}
which is a member of
public class B
{
public A NonUIElement {get; set;}
}
Is it possible to render this object via a Binding such as
<Border Name="Border">
<ContentPresenter Margin="5,0" Content="{Binding NonUIElement }"/>
</Border>
assuming the DataContext of border is set to an instance of B?
The object will render, but not in the way I think you're hoping for. The Content of the ContentPresenter is set to the instance of A. WPF then tries to figure out how to render this instance of A. It first asks, is this object a UIElement? In this case, the answer is no. So it next looks for a DataTemplate for the type. In this case, there's no DataTemplate for the A class. So it falls back on calling ToString(). So your ContentPresenter will display a TextBlock containing the text "YourNamespace.A".
The fact that A happens to have a member of type ControlTemplate does not affect this logic. To WPF, that's just a chunk of data that A happens to be carrying around. WPF only uses ControlTemplate when there is a Control involved and the ControlTemplate is assigned to the Template property.
So you need either to supply a DataTemplate for A (which of course can access the ControlTemplate and use it to render the instance), or create a named DataTemplate and apply that via ContentPresenter.ContentTemplate, or derive from UIElement instead.
I finally got it with this;
<HierarchicalDataTemplate DataType="{x:Type vm:MapLayerModel}" ItemsSource="{Binding Path=Children, Mode=OneTime}">
**<ContentControl Margin="5" Content="{Binding LayerRepresentation}" Template="{Binding LayerRepresentation.ControlTemplate}" Mode=OneTime/>**
</HierarchicalDataTemplate>
This has been a great personal lesson on WPF templating and its content control model. Thanks again itowlson.

Resources