WPF - MVVM - Conditionally include user controls to view - wpf

In WPF my MVVM application I need to create an account search view with 2 options simple search by account # or Advanced search (by name, email, etc.)
In my AccountSearchViewModel I have a bool property IsAdvancedMode.
Also I have created 2 UserControls for each mode: SimpleSearchView and AdvancedSearchView
Now I need to show either one based on IsAdvancedMode property.
What is the best way to do it?
Also as a general solution what if I have SearchMode property that is enum. How whould you switch between multiple controls in that case?

I think you need to use Data Templating, to do that you need to create three classes:
public class Search
{
//Your Code
}
public class AdvanceSearch : Search
{
//Your Code
}
public class SimpleSearch : Search
{
//Your Code
}
and then create Data Template base on Classes:
<DataTemplate DataType="{x:Type local:AdvanceSearch }">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Email}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SimpleSearch }">
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>

I would use a DataTrigger to swap out the ContentTemplate of a ContentControl as needed. I wrote an article about switching Views in MVVM here if you're interested (examples included)
Here's some quick test code demonstrating it:
<Window.Resources>
<DataTemplate x:Key="TemplateA" >
<TextBlock Text="I'm Template A" />
</DataTemplate>
<DataTemplate x:Key="TemplateB" >
<TextBlock Text="I'm Template B" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<ToggleButton x:Name="Test" Content="Test" />
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateA}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Test, Path=IsChecked}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource TemplateB}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</StackPanel>

I usually drop them both and then use the BooleanToVisibilityConverter. Simplest approach with what you've got set up.
<Grid>
<SimpleSearch />
<AdvancedSearch
Visibility="{Binding IsAdvancedMode, Converter={StaticResource btvc}"/>
</Grid>
When IsAdvancedMode is true, the AdvancedSearch control will overlay the SimpleSearch. Again, this is the simplest approach, not necessarily the absolute best.

Related

ListBox item template depends on item values

In WPF, MVVC applicaiton I have quite normal listbox:
<ListBox ItemsSource="{Binding Friends}">
</ListBox>
Then each item of list uses following datatemplate:
<DataTemplate DataType="{x:Type model:Friend}">
<Border BorderThickness="1" BorderBrush="Gray" Padding="3" Name="border" Margin="3">
<StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
<TextBlock Name="DescriptionDTDataType" Text="{Binding Path=ConnectedUserID}" />
<TextBlock Name="StatusTitle" Margin="8,0,4,0">Status:</TextBlock>
<TextBlock Name="Status" Text="{Binding Path=Approved}" />
<Button Content="Approve" Command="{x:Static inf:Commands.ApproveUserConnection}" CommandParameter="{Binding}"></Button>
<Button Content="Disapprove" Command="{x:Static inf:Commands.DisapproveUserConnection}" CommandParameter="{Binding}"></Button>
</StackPanel>
</Border>
</DataTemplate>
The question is... I want to hide one of the buttons on the basic of Friend.Approved property. For example if Friend.Approved has value "approved" I want to hide button Approved and show only button Dissaprove. On the other hand if Friend.Approved has value "disapproved" then I want opposite. How to achieve this?
Thank you.
Beside creating a IValueConverter there is a pure XAML solution using DataTriggers
Just add this to your DataTemplate:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Approved}" Value="approved" >
<Setter TargetName="Approve" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
<DataTrigger Binding="{Binding Approved}" Value="disapproved" >
<Setter TargetName="Disapprove" Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</DataTemplate.Triggers>
And name your buttons:
<Button Name="Approve" Content="Approve" ...></Button>
<Button Name="Disapprove" Content="Disapprove" ...></Button>
However if you want to reuse this hiding logic in multiple places in multiple DataTemplates its better to write the Approved text to Visibility converter.
Bind the Buttons Visibility property to Approved and use a value converter to convert Approved to a Visibility value.

Different views / data template based on member variable

I have a view model called
ViewModelClass
which contains a boolean.
I have another view model which contains
ObservableCollection<ViewModelClass> m_allProjects;
Then I have this in my view:
<DataTemplate>
<views:ProjectInfoView x:Key="ProjectInfoDetailTemplate"/>
</DataTemplate>
<ItemsControl Grid.Row="1" Grid.Column="0"
ItemsSource="{Binding AllProjects}"
ItemTemplate="{StaticResource ProjectInfoDetailTemplate}"
Margin="10,28.977,10,10">
</ItemsControl >
I want, based on the boolean in the AllProjects-collection, to use a different datatemplate. What is the best way to do this?
I know I can do this with different ViewModels and use a kind of ViewModel-base object, but I prefer just to use 1 view model.
EDIT:
I want to do this with data triggers. Can someone provide me with some code please?
I usually use a ContentControl to display the data, and swap out the ContentTemplate in a trigger based on the property that changes.
Here's an example I have posted on my blog that swaps a template based on a bound property
<DataTemplate x:Key="PersonTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Person" />
</DataTemplate>
<DataTemplate x:Key="BusinessTemplate" DataType="{x:Type local:ConsumerViewModel}">
<TextBlock Text="I'm a Business" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ConsumerViewModel}">
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource PersonTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ConsumerType}" Value="Business">
<Setter Property="ContentTemplate" Value="{StaticResource BusinessTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
A DataTemplateSelector will also work, but only if the property that determines which template to show doesn't change since DataTemplateSelectors don't respond to change notifications. I usually avoid them if possible since I also prefer my view selection logic in my view so I can see whats going on.

Set a Usercontrol as a Datatemplate

I have the following ProductList template snipet
<Style x:Key="ProductListStyle" TargetType="{x:Type s:SurfaceListBox }">
<Setter Property="Background" Value="{DynamicResource {x:Static s:SurfaceColors.ListBoxItemBackgroundBrushKey}}" />
<Setter Property="SelectionMode" Value="Single" />
<Setter Property="Height" Value="234" />
<Setter Property="ItemTemplateSelector">
<Setter.Value>
<sc:ProductListTemplateSelector>
<sc:ProductListTemplateSelector.NormalItemTemplate>
<DataTemplate>
<StackPanel RenderTransformOrigin="0.5, 0.5"
Margin="7,0,0,0"
MinWidth="171" MaxWidth="171"
MinHeight="235" MaxHeight="235">
<Image Margin="14,21,21,11" Source="{Binding XPath=#Image}"
Height="149" Width="101" />
<TextBlock Text="{Binding XPath=#Name}"
MaxWidth="116"
FontSize="12"
Margin="21,0,21,21"
FontFamily="Segoe360"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource {x:Static s:SurfaceColors.ListBoxItemForegroundBrushKey}}"
HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</sc:ProductListTemplateSelector.NormalItemTemplate>
I need to replace the DataTemplate of this style to be set using my user control like
<local:MyUserControl>
By keeping only between section I did not get my control displayed when my Itemsource is set with a collection of myUserControl
Usually I just add the DataTemplate in the Resources. This can be <Window.Resources> or <App.Resources> if the data template is global, or FrameworkElement.Resources if the template should only be applied in a specified scope. For example, adding the template to ListView.Resources would only apply the template within a specific ListView.
<Window.Resources>
<DataTemplate DataType="{x:Type local:ProductModel}">
<local:MyUserControl />
</DataTemplate>
</Window.Resources>
As a side note, your original question leads me to believe that you are binding a ListView to a collection of MyUserControl objects. I really wouldn't recommend this, but if this is the case you can use a ContentControl in your DataTemplate with it's Content bound to your object, and it should display correctly.
<ContentControl Content="{Binding }" />

XAML controls binding with another controls which are in datatemplate !

i have the following datatemplate in xaml and here i have textbox in datatemplate and button in normal XAML, what i really want is that i want to keep button be disabled until person enters something in textbox ? but its not working i have tried the follwing code
Please look into it and help me as well ! or is there any other way to do this let me know !
<UserControl.Resources>
<DataTemplate x:Key="dataTemplateParameter">
<StackPanel Width="170" Height="Auto" Margin="5">
<TextBlock Width="160" TextAlignment="Left" HorizontalAlignment="Left" Height="22" Text="{Binding Path=ParameterName, Mode=TwoWay}"/>
<TextBox x:Name="txtboxParameter" Width="160" Height="22" HorizontalAlignment="Left" Text="{Binding Path=ParameterValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</DataTemplate>
<Button Grid.Row="6" Name="SearchSourceParamBtn" Content="Search" VerticalAlignment="Bottom" Margin="12,5,92,0" Height="20" Visibility="{Binding SearchSourceBtnVisibility}" Command="{Binding SearchCommand}">
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text,x:ElementName=txtboxParameter}" Value="">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Thanks
nallskarthi
You can achieve this by
1) binding button to RelayCommand. Then,
2) Validate the "ParameterValue" in command's CanExecute delegate.
Edit:
The better way is to have the ParameterValue in ViewModel, but for some reason you won't do that. Then, create a static ChangeNotifyable property in VM, using this property do the validation in CanExecute delegate.
Pls Validate the "ParameterValue" in the CanExecute delegate of Command object.

How can I use a custom TabItem control when databinding a TabControl in WPF?

I have a custom control that is derived from TabItem, and I want to databind that custom TabItem to a stock TabControl. I would rather avoid creating a new TabControl just for this rare case.
This is what I have and I'm not having any luck getting the correct control to be loaded. In this case I want to use my ClosableTabItem control instead of the stock TabItem control.
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True"
Controls:ClosableTabItem.TabClose="TabClosed" >
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type Controls:ClosableTabItem}" >
<TextBlock Text="{Binding Path=Id}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
EDIT: This is what I ended up with, rather than trying to bind a custom control.
The "CloseCommand" im getting from a previous question.
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border
Name="Border"
Background="LightGray"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="25,0,0,0"
SnapsToDevicePixels="True">
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="20,1,5,1"/>
<Button
Command="{Binding Path=CloseCommand}"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
Margin="1,1,5,1"
Background="Transparent"
BorderThickness="0">
<Image Source="/Russound.Windows;component/Resources/Delete.png" Height="10" />
</Button>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter TargetName="Border" Property="Background" Value="LightBlue" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter TargetName="Border" Property="BorderBrush" Value="DarkBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
found a way,
derive a class from TabControl and override this function, in my case I want the items of the tab control (when bound) to be CloseableTabItems
public class CloseableTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CloseableTabItem();
}
}
HTH Someone
Sam
You don't want to set the DataType of the DataTemplate in this case. The value of the ItemTemplate property is used whenever a new item needs to be added, and in the case of a tab control it will be used to create a new TabItem. You should declare an instance of your class within the DataTemplate itself:
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:ClosableTabItem>
<TextBlock Text="{Binding Path=Id}" />
</Controls:ClosableTabItem>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This will cause a new ClosableTabItem to be created whenever a new tab is added to the TabControl.
Update; From your comment, it sounds like that the ItemTemplate controls what is created within the TabItem, rather than changing the TabItem itself. To do what you want to do, but for a TreeView, you would set the HeaderTemplate. Unfortunately, I don't see a HeaderTemplate property of TabControl.
I did some searching, and this tutorial modifies the contents of the tab headers by adding controls to TabItem.Header. Maybe you could create a Style for your TabItems that would add the close button that your class is currently adding?

Resources