ContentTemplateSelector is only called one time showing always the same datatemplate - wpf

I have made a sample demo VS 2010 RC sample project, because in my production project I have the same error using MVVM.
In my sample demo project I use only Code-behind without 3rd party dependencies so you can download the demo project here and run it for yourself: http://www.sendspace.com/file/mwx7wv
Now to the problem: When I click the girls/boys button it should switch the datatemplate, not?
What do I wrong?
OK I offer here a code snippet too:
Code-Behind MainWindow.cs:
namespace ContentTemplateSelectorDemo
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Person person;
public MainWindow()
{
InitializeComponent();
person = new Person(){ Gender = "xxx"};
person.IsBoy = true;
ContentGrid.DataContext = person;
}
private void btnBoys_Click(object sender, RoutedEventArgs e)
{
person.IsBoy = true;
person.IsGirl = false;
this.ContentGrid.DataContext = person;
}
private void btnGirls_Click(object sender, RoutedEventArgs e)
{
person.IsGirl = true;
person.IsBoy = false;
this.ContentGrid.DataContext = person;
}
}
}
XAML MainWindow.xaml:
<Window x:Class="ContentTemplateSelectorDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ContentTemplateSelectorDemo"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="girlsViewTemplate">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="boysViewTemplate" >
<local:UserControl2 />
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="PersonSelector" />
</Window.Resources>
<Grid x:Name="ContentGrid" >
<StackPanel>
<Button Name="btnGirls" Click="btnGirls_Click">Switch Girls</Button>
<Button Name="btnBoys" Click="btnBoys_Click">Switch Boys</Button>
<ContentControl Content="{Binding}" ContentTemplateSelector="{StaticResource ResourceKey=PersonSelector}" />
</StackPanel>
</Grid>
</Window>
DataTemplateSelector class:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item,DependencyObject container)
{
if (item is Person)
{
Person person = item as Person;
Window window = Application.Current.MainWindow;
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode( window))
return null;
if (person.IsBoy)
return window.FindResource("boysViewTemplate") as DataTemplate;
if (person.IsGirl)
return window.FindResource("girlsViewTemplate") as DataTemplate;
}
return null;
}
}
:)

I like Neil's solution (found on Josh's post via the link you provided):
<DataTemplate DataType="{x:Type local:MyType}">
<ContentPresenter Content="{Binding}" Name="cp" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsRunning}" Value="True">
<Setter TargetName="cp" Property="ContentTemplate" Value="{StaticResource StopTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsRunning}" Value="False">
<Setter TargetName="cp" Property="ContentTemplate" Value="{StaticResource StartTemplate}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
Edit: I couldn't actually get the above code to work, but this works using a style:
<ContentControl DockPanel.Dock="Bottom" >
<ContentControl.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SourceSystem.SourceSystemName}" Value="mysite.com">
<Setter Property="ContentControl.ContentTemplate" Value="{StaticResource mysiteToolbar}" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=SourceSystem.SourceSystemName}" Value="mysite2.com">
<Setter Property="ContentControl.ContentTemplate" Value="{StaticResource mysiteToolbar2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Note: I think this method is quite clumsy, but could work for some scenarios. I favor the method of using a trigger (from Neil) that I posted as a separate answer.
Another possible way is to bind the Content of the ContentTemplateSelector to the property that determines the template that should be selected. For instance here I have two different toolbars chosen based upon the value of SourceSystem. I set the
Content to be the sourcesystem property itself.
<ContentControl ContentTemplateSelector="{StaticResource toolbarTemplateSelector}"
DataContext="{Binding}" Content="{Binding SourceSystem}" />
The template selector simply looks at the source system and returns the necessary template.
If the template needs access to the datacontext of the control, just use element binding to set it.
<UserControl.Resources>
<DataTemplate x:Key="toolbar1">
<views:OrdersToolbar1View Margin="0,5,0,0"
DataContext="{Binding ElementName=control,Path=DataContext}"/>
</DataTemplate>
<DataTemplate x:Key="toolbar2">
<views:OrdersToolbar2View Margin="0,5,0,0"
DataContext="{Binding ElementName=control,Path=DataContext}"/>
</DataTemplate>
</UserControl.Resources>

Use this method for custom Content Selector:
private void ReloadContent()
{
MainContentControl.ContentTemplate = MainContentControl.ContentTemplateSelector.SelectTemplate(null, MainContentControl);
}
In xaml:
<ContentControl Content="{Binding}" x:Name="MainContentControl">
<ContentControl.ContentTemplateSelector >
<templateSelectors:MainViewContentControlTemplateSelector>
<templateSelectors:MainViewContentControlTemplateSelector.BoysTemplate>
<DataTemplate>
<local:UserControl1 />
</DataTemplate>
</templateSelectors:MainViewContentControlTemplateSelector.BoysTemplate>
<templateSelectors:MainViewContentControlTemplateSelector.GirlsTemplate>
<DataTemplate>
<local:UserControl2 />
</DataTemplate>
</templateSelectors:MainViewContentControlTemplateSelector.GirlsTemplate>
</ContentControl>
And Selector :
public class MainViewContentControlTemplateSelector : DataTemplateSelector
{
public DataTemplate BoysTemplate{ get; set; }
public DataTemplate GirlsTemplate{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var contentControl = container.GetVisualParent<ContentControl>();
if (contentControl == null)
{
return BoysTemplate;
}
if (//Condition)
{
return GirlsTemplate;
}
return BoysTemplate;
}

Related

How to binding different data context to data template?

I try to simplify my Main user control that contains 8 user controls that are exactly the same but they are binding to different VM to display its data.
Currently, I have to create a template for each of my user control and binding to each of VM.
It seems that I can create one data template for all 8 user controls and apply the data template to each of the user control with different instance of VM.
Here are my code that current I have to use different templates for different dependency of View Model containing the data of each gauge
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue1VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<DataTemplate x:Key="AnalogIO1Template" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue2VMDP.GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO1Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
<Grid Background="#FFE3E2D7" Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl x:Name="ucLinearGauge2">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIO2Template }" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Here I try to create one Data template for all 8 user controls but it does not work
<DataTemplate x:Key="AnalogIOTemplate" DataType="{x:Type local:CAnalogIOVM}">
<local:ucAnalogIO
GaugeValueDP="{Binding Path=GaugeValue, ElementName=ucAnalogIOWindow}">
</local:ucAnalogIO>
</DataTemplate>
<Grid Background="#FFE3E2D7" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Grid.RowSpan="1" Margin="0,0,0,1">
<ContentControl Content="{Binding Path=GaugeValue1VMDP}" x:Name="ucLinearGauge1">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource AnalogIOTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Is there a way to binding different data context to the data template?
Thanks
If class of UserControl, class of VM and bindings between them are identical and the only difference is instances of VM, creating a Style for UserControl and binding each instance of VM with DataContext of corresponding instance of UserControl would be enough.
Since we don't know actual code of your UserControl and VM, I will show this by samples.
Sample UserControl which has Id dependency property and can show its value:
<UserControl x:Class="WpfApp.SampleUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel>
<TextBlock x:Name="IdTextBlock"/>
</StackPanel>
</UserControl>
public partial class SampleUserControl : UserControl
{
public int Id
{
get { return (int)GetValue(IdProperty); }
set { SetValue(IdProperty, value); }
}
public static readonly DependencyProperty IdProperty =
DependencyProperty.Register("Id", typeof(int), typeof(SampleUserControl),
new PropertyMetadata(0, (d, e) => ((SampleUserControl)d).IdTextBlock.Text = e.NewValue.ToString()));
public SampleUserControl()
{
InitializeComponent();
}
}
Sample VM which has Id property and MainWindow's VM which has instances of sample VM:
// using Microsoft.Toolkit.Mvvm.ComponentModel;
public class SampleViewModel : ObservableObject
{
private int _id;
public int Id
{
get => _id;
set => SetProperty(ref _id, value);
}
}
public class MainWindowViewModel : ObservableObject
{
public SampleViewModel? VM1 { get; }
public SampleViewModel? VM2 { get; }
public MainWindowViewModel()
{
VM1 = new SampleViewModel { Id = 10 };
VM2 = new SampleViewModel { Id = 20 };
}
}
Finally, bind each instance of sample VM with DataContext of corresponding instance of sample UserControl so that Id of sample VM is bound with Id of sample UserControl.
<Window x:Class="WpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp"
Title="MainWindow"
Width="400" Height="200">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="{x:Type local:SampleUserControl}">
<Setter Property="Id" Value="{Binding Id}"/>
</Style>
</Window.Resources>
<StackPanel>
<local:SampleUserControl DataContext="{Binding VM1}"/>
<local:SampleUserControl DataContext="{Binding VM2}"/>
</StackPanel>
</Window>

WPF Switching Views through Command Button

I'm quite new to WPF and what I'm trying to do is switch views through different command buttons.. this is my current code...
Basically I have two buttons in my MainView, SetupLan and SetupSerialPort.
For simplicity, I'm currently trying to make SetupSerialPort working first. But when I click the button for it, the command executes but the view doesn't change...
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupLanViewModel}">
<Views:SetupLanView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupSerialPortViewModel}">
<Views:SetupSerialPortView />
</DataTemplate>
</Application.Resources>
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding}" />
</Grid>
MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
MainViewModel
public DisplaySetupSerialPortCommand SetupSerialPort
{
get; set;
}
public MainViewModel()
{
SetupSerialPort = new DisplaySetupSerialPortCommand(this);
}
MainView
<StackPanel>
<Button Height="50" Width="150" HorizontalAlignment="Left"Command="{Binding SetupLan}">Lan</Button>
<Button Height="50" Width="150" HorizontalAlignment="Left" Command="{Binding SetupSerialPort}">Serial Port</Button>
</StackPanel>
DisplaySetupSerialPortCommand
class DisplaySetupSerialPortCommand : ICommand
{
private BaseViewModel _baseViewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public DisplaySetupSerialPortCommand(BaseViewModel baseViewModel)
{
_baseViewModel = baseViewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_baseViewModel.ViewModel = new SetupSerialPortViewModel();
}
}
A few things.
Your command is not doing anything at all, it's simply just creating a ViewModel. You need to add additional logic for this to work.
If you want to switch out the DataTemplate to different views, you will need to implement some type of Trigger.
First, update your DataTemplates to have an associated key.
<Application.Resources>
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}" x:Key="MainView">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupLanViewModel}" x:Key="Lan">
<Views:SetupLanView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:SetupSerialPortViewModel}" x:Key="SerialPort">
<Views:SetupSerialPortView />
</DataTemplate>
</Application.Resources>
By adding a key, it will allow you to reference each one, where/when needed.
Next, inside your MainWindow.xaml, this is where the Triggers will occur.
Update your ContentControl to Trigger and switch it's DataTemplate based on some condition. In this case, the condition I created is called ViewType.
<Grid>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource MainView}" />
<Style.Triggers>
<DataTrigger Binding="{Binding ViewType}" Value="SerialPort">
<Setter Property="ContentTemplate" Value="{StaticResource SerialPort}" />
</DataTrigger>
<DataTrigger Binding="{Binding ViewType}" Value="Lan">
<Setter Property="ContentTemplate" Value="{StaticResource Lan}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
Lastly, you will need to have a ViewType property, and set that property based on the specific command that was executed. Add to ViewType property your BaseViewModel since the command has a reference to this.
public string ViewType
{
get { return _viewType; }
set
{
_viewType = value;
RaisePropertyChanged();
}
}
Now, you can change the ViewType whenever the command is clicked.
public void Execute(object parameter)
{
_baseViewModel.ViewType = "SerialPort";
_baseViewModel.ViewModel = new SetupSerialPortViewModel();
}
Note: I used a string for a ViewType type just for demo purposes only. You would probably want to use an enumeration instead.

ContentPresenter's Binding not updated when bound object is updated

I have a master view and two subviews. I would like to switch from SubViewA to SubViewB when clicking on the button on SubViewA. The masterview contains a contentpresenter which is binded to View, and initialized to SubViewB when loaded. When clicking on the button on SubViewA the SubViewB constructor is called, but the control is never loaded. What am I missing? I've also tried by just setting the contenttemplate:
<ContentPresenter x:Name="contentPresenter" Content="{Binding View, PresentationTraceSources.TraceLevel=High}" />
which does not work either.
MainWindow:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication2">
<Grid>
<TextBlock Text="MasterViewPage" />
<ContentControl x:Name="content" Content="{Binding View}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:SubViewModelA}">
<local:SubViewA></local:SubViewA>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SubViewModelB}">
<local:SubViewB></local:SubViewB>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
public partial class MainWindow
{
public MainWindow()
{
Loaded += MainWindow_Loaded;
InitializeComponent();
}
private void MainWindow_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
DataContext = new MainViewModel();
}
}
SubViewA:
<UserControl x:Class="WpfApplication2.SubViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Margin="0,40,0,0">
<TextBlock Text="Subview A" />
<Button Height="50" Width="120" Content="Open View B" Command="{Binding OpenViewCommand}" />
</Grid>
</UserControl>
public partial class SubViewA
{
public SubViewA()
{
Loaded += SubViewA_Loaded;
InitializeComponent();
}
private void SubViewA_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
DataContext = new SubViewModelA();
}
}
ViewModels:
public class MainViewModel : NotifyPropertyChanged
{
private object _view;
public object View
{
get { return _view; }
set
{
_view = value;
RaisePropertyChanged(() => View);
}
}
public MainViewModel()
{
View = new SubViewA();
}
}
public class SubViewModelA : MainViewModel
{
public ICommand OpenViewCommand
{
get { return new DelegatingCommand(OpenView); }
}
private void OpenView()
{
View = new SubViewB();
}
}
public class SubViewModelB : MainViewModel
{
}
Thanks in advance.
View-models should not contain references to the view, instead have a property ViewMode which can be an enum and trigger on that, here is an example (you can set the ContentTemplate instead of Content as well).
OK, the solution that worked for me was:
MainView.xaml:
<ContentControl x:Name="content">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding View, PresentationTraceSources.TraceLevel=High}" Value="SubViewA">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:SubViewA />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding View, PresentationTraceSources.TraceLevel=High}" Value="SubViewB">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<local:SubViewB />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
and in SubViewA.xaml:
<Button Height="50" Width="120" Content="Open View B" Command="{Binding Path=DataContext.OpenViewCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}, PresentationTraceSources.TraceLevel=High}" />
Reason was that View wasn't set on MainViewModel, but on the subview instead (which inherited from MainViewModel). When removing the inheritance to actually set the view on MainViewModel everything worked.
I can agree on that it's not good to set the view in model directly. But anyway, I tried your solution (both of them) and SubViewB is still not loaded. The constructor is called, but never SubViewB_Loaded. So, the result is that the SubViewB is never shown.
The datacontextchanged is never triggered on the contentcontrol. So, still, I'm missing something.
The main view:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication2">
<Grid>
<TextBlock Text="MasterViewPage" />
<ContentControl x:Name="content">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding View}" Value="SubViewA">
<Setter Property="Content">
<Setter.Value>
<local:SubViewA />
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding View}" Value="SubViewB">
<Setter Property="Content">
<Setter.Value>
<local:SubViewB />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</Window>
And in the viewmodel:
public class MainViewModel : NotifyPropertyChanged
{
private string _view;
public string View
{
get { return _view; }
set
{
_view = value;
RaisePropertyChanged(() => View);
}
}
public MainViewModel()
{
View = "SubViewA";
}
}
public class SubViewModelA : MainViewModel
{
public ICommand OpenViewCommand { get { return new DelegatingCommand(OpenView); } }
private void OpenView()
{
View = "SubViewB";
}
}
public class SubViewModelB : MainViewModel
{
}

Bind TextBox.Text in Watermark attached property to localize it

I need to add Watermarks to my WPF application and I am using this watermark service to achieve it.
It works just as expected, but I need to localize the text showed as watermark and I can't find a way to achieve it.
My xaml declaration:
<TextBox
Text="{Binding Name, Mode=TwoWay}">
<Watermark:WatermarkService.Watermark>
<TextBlock
Text="{Binding <What-goes-here?>}"
/>
</Watermark:WatermarkService.Watermark>
</TextBox>
My ViewModel:
public class PersonViewModel
{
public string Name {get;set;}
public string NameWatermark {get;set;}
}
Thw following lines do NOT work:
Text="{Binding NameWatermark}"
<Window x:Name="ThisWindow"
...
Text="{Binding Path=NameWatermark,
ElementName=ThisWindow}"
Is there any way to achieve it?
Any help would be appreciated, thanks.
I realize this question is ancient, but for google time-travelers, i hit the exact same problem and solved it by using a MultiDataTrigger to hide/show the watermark element. It works because it's not embedding the element within an adorner like the original Watermark:WatermarkService.Watermark does.
<Grid>
<TextBox x:Name="messageBox" Text="{Binding Message, UpdateSourceTrigger=PropertyChanged}" Padding="5">
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand}"/>
</TextBox.InputBindings>
</TextBox>
<TextBlock Text="{text:Translate Key=WriteMessageWatermark}" IsHitTestVisible="False" Foreground="LightGray" Margin="7,6,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=messageBox, Path=Text.IsEmpty}" Value="True" />
<Condition Binding="{Binding ElementName=messageBox, Path=IsFocused}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
I think the issue is that the WatermarkService is just a static class and no dependencyobject and therefore doesn't inherit the DataContext properly to its content.
At first: Do you really need to bind to the ViewModel? I assume you want to localize more parts of your UI so moving this stuff to a dedicated service would be an option.
Something like this:
<Watermark:WatermarkService.Watermark>
<TextBlock Text="{Binding Source={x:Static Watermark:LocalizationService.Instance}, Path=[123],FallbackValue='No LocalizationService found'}"/>
</Watermark:WatermarkService.Watermark>
Singleton class for Localization:
private static volatile LocalizationService _instance;
private static object syncRoot = new Object();
public static LocalizationService Instance
{
get
{
if (_instance == null)
{
lock (syncRoot)
{
if (_instance == null)
_instance = new LocalizationService();
}
}
return _instance;
}
}
public string this[int id]
{
get
{
// do localization stuff here ...
return "Localized Value " + id;
}
}
}
If you really want to bind to the window's DataContext you can use the following workaround by assigning the ViewModel to an ObjectDataprovider and access it in your Binding:
<Grid Loaded="Grid_Loaded">
<Grid.Resources>
<ObjectDataProvider x:Key="PersonViewModelProvider" ObjectInstance="{x:Null}" IsAsynchronous="True"/>
</Grid.Resources>
<TextBox Text="{Binding Name, Mode=TwoWay}">
<Watermark:WatermarkService.Watermark>
<TextBlock Text="{Binding Source={StaticResource PersonViewModelProvider}, Path=NameWatermark}"/>
</Watermark:WatermarkService.Watermark>
</TextBox>
</Grid>
Code Behind:
private void Grid_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
var panel = sender as Panel;
if (panel != null)
{
var objDataProvider = panel.Resources["PersonViewModelProvider"] as ObjectDataProvider;
objDataProvider.ObjectInstance = panel.DataContext;
}
}

Selecting a ListBoxItem when its inner ComboBox is focused

I have a DataTemplate that will be a templated ListBoxItem, this DataTemplate has a
ComboBox in it which when it has focus I want the ListBoxItem that this template
represents to become selected, this looks right to me. but sadly enough it doesn't work =(
So the real question here is within a DataTemplate is it possible to get or set the value
of the ListBoxItem.IsSelected property via a DataTemplate.Trigger?
<DataTemplate x:Key="myDataTemplate"
DataType="{x:Type local:myTemplateItem}">
<Grid x:Name="_LayoutRoot">
<ComboBox x:Name="testComboBox" />
</Grid>
<DataTemplate.Triggers>
<Trigger Property="IsFocused" value="true" SourceName="testComboBox">
<Setter Property="ListBoxItem.IsSelected" Value="true" />
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" />
I found a solution for your problem.
The problem is that when you have a control on your listboxitem, and the control is clicked (like for inputting text or changing the value of a combobox), the ListBoxItem does not get selected.
this should do the job:
public class FocusableListBox : ListBox
{
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is FocusableListBoxItem);
}
protected override System.Windows.DependencyObject GetContainerForItemOverride()
{
return new FocusableListBoxItem();
}
}
--> Use this FocusableListBox in stead of the default ListBox of WPF.
And use this ListBoxItem:
public class FocusableListBoxItem : ListBoxItem
{
public FocusableListBoxItem()
{
GotFocus += new RoutedEventHandler(FocusableListBoxItem_GotFocus);
}
void FocusableListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
object obj = ParentListBox.ItemContainerGenerator.ItemFromContainer(this);
ParentListBox.SelectedItem = obj;
}
private ListBox ParentListBox
{
get
{
return (ItemsControl.ItemsControlFromItemContainer(this) as ListBox);
}
}
}
A Treeview does also have this problem, but this solution does not work for a Treeview, 'cause SelectedItem of Treeview is readonly.
So if you can help me out with the Treeview please ;-)
I found that I preferred to use this:
<Style TargetType="ListBoxItem">
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True"></Setter>
</Trigger>
</Style.Triggers>
</Style>
Simple and works for all the listboxitems, regardless of what's inside.
No idea why your trigger don't work. To catch the get focus event of the combo box (or any control inside a listbox item) you can use attached routed events. You could put the code also in a derived listbox if you need this behavior in other parts of your application.
XAML:
<Window x:Class="RoutedEventDemo.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Specialized="clr-namespace:System.Collections.Specialized;assembly=System"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="myDataTemplate">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" Margin="5,0"/>
<ComboBox Width="50">
<ComboBoxItem>AAA</ComboBoxItem>
<ComboBoxItem>BBB</ComboBoxItem>
</ComboBox>
</StackPanel>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemTemplate="{StaticResource myDataTemplate}">
<ListBox.ItemsSource>
<Specialized:StringCollection>
<System:String>Item 1</System:String>
<System:String>Item 2</System:String>
<System:String>Item 3</System:String>
</Specialized:StringCollection>
</ListBox.ItemsSource>
</ListBox>
</Grid>
</Window>
Code behind hooking up to all got focus events.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace RoutedEventDemo
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
EventManager.RegisterClassHandler(typeof(UIElement),
GotFocusEvent,
new RoutedEventHandler(OnGotFocus));
}
private static void OnGotFocus(object sender, RoutedEventArgs e)
{
// Check if element that got focus is contained by a listboxitem and
// in that case selected the listboxitem.
DependencyObject parent = e.OriginalSource as DependencyObject;
while (parent != null)
{
ListBoxItem clickedOnItem = parent as ListBoxItem;
if (clickedOnItem != null)
{
clickedOnItem.IsSelected = true;
return;
}
parent = VisualTreeHelper.GetParent(parent);
}
}
}
}

Resources