Sooner or later any wpf programmer begin to use BindingProxy.
I am trying to split xaml by moving some of resources into separate resource dictionary. My problem is that resources contain reference to BindingProxy.
How can I handle this situation?
As an example, lets say there is a resource with BindingProxy which is used somewhere
<Window.Resources>
<local:BindingProxy x:Key="proxy" />
<ControlTemplate x:Key="test">
<TextBlock Text="{Binding DataContext.Test, Source={StaticResource proxy}}" />
</ControlTemplate>
</Window.Resources>
<Control Template="{StaticResource test}" />
and code behind
public partial class MainWindow : Window
{
public string Test { get; set; } = "Test 123";
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
It may not be the best example, using BindingProxy is not really justified, but it serve demonstration purpose well. During run-time window with text "Test 123" will be shown.
Now lets try move resource to resource dictionary Dictionary1.xaml
<ResourceDictionary ... >
<ControlTemplate x:Key="test">
<TextBlock Text="{Binding Test, Source={StaticResource proxy}}" /> <!-- error here -->
</ControlTemplate>
</ResourceDictionary>
and change main window resource to
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
<local:BindingProxy x:Key="proxy" />
</ResourceDictionary>
</Window.Resources>
<Control Template="{StaticResource test}" />
will lead to desinger and run-time exception
System.Windows.Markup.XamlParseException: ''Provide value on 'System.Windows.Markup.StaticResourceHolder' threw an exception.' Line number '5' and line position '20'.'
Inner Exception
Exception: Cannot find resource named 'proxy'. Resource names are case sensitive.
How can I reference proxy? Is there another technique exist to reference somethining from resource dictionary? Maybe some kind of RelativeResource approach but for things which are not in visual tree? I can't move proxy into ResourceDictionary1.xaml for obvious reasons: it will not capture DataContext of window.
Even though I don't recommend the BindingProxy in MVVM, this is how I think your problem is resolved:
Bear in mind when you include a ResourceDictionary in your view XAML, it automatically inherits the DataContext of the view hence you can keep the BindingProxy in the ResourceDictionary but you need to specify the binding explicitly.
Remember to also remove the proxy declaration from the View XAML as it is now in the dictionary.
You lose the ability to change the DataContext of the BindingProxy, it will use the DataContext of the consumer view.
ResourceDictionary:
<ResourceDictionary ...>
<!-- NOTE: Data property grabs the DataContext of the consumer view -->
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<ControlTemplate x:Key="text">
<TextBlock Text="{Binding Data.Test, Source={StaticResource proxy}}" />
</ControlTemplate>
</ResourceDictionary>
Window:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
Snapshot:
Do you really need the BindingProxy?
The suggestion above defeats the purpose of the BindingProxy as it is not needed anymore; note you could just change the ResourceDictionary as follows and it works exactly the same without any need of the BindingProxy:
<ResourceDictionary ...>
<ControlTemplate x:Key="test">
<TextBlock Text="{Binding Test}" />
</ControlTemplate>
</ResourceDictionary>
Related
I have a HeaderedContentControl in my application. In this HeaderedContentControl there is a TabControl.
Now I want to display the name of the selected tab (TabItem) in the header of the HeaderedContentControl (like in Visual Studio the Solution Explorer / Team Explorer e.g.).
My application is (partly) based on Josh Smiths WPF-MVVM-Example but I use additional Prism with Unity.
Furthermore I split the resources in some files.
Here is my MainView.xaml:
<UserControl x:Class="STController.ModuleAComport.View.MainView"
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">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="..\Resources\MainViewResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Resources>
</Grid.Resources>
<Border Grid.Row="0"
Grid.Column="0"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl Header="?"
Style="{StaticResource MainHeadreredContentControlStyle}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Content="{Binding Path=Workspaces}" />
</Border>
</Grid>
</UserControl>
And here is my MainViewResources.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:STController.ModuleAComport.View"
xmlns:viewmodel="clr-namespace:STController.ModuleAComport.ViewModel">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/STController.Resources;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Put your not shared resource here -->
<DataTemplate DataType="{x:Type viewmodel:ComportViewModel}">
<view:ComportView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodel:TestViewModel}">
<view:TestView />
</DataTemplate>
<DataTemplate x:Key="TabItemTemplate">
<Grid>
<ContentPresenter VerticalAlignment="Center"
Content="{Binding Path=DisplayName}" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl x:Name="TabControl"
IsSynchronizedWithCurrentItem="True"
TabStripPlacement="Bottom"
Style="{StaticResource MainTabControlStyle}"
ItemsSource="{Binding}"
Margin="4"
ItemTemplate="{StaticResource TabItemTemplate}"
ItemContainerStyle="{StaticResource MainTabItemStyle}">
</TabControl>
</DataTemplate>
</ResourceDictionary>
I had two different solution approaches:
1) Binding the SelectedIndex:
My first idea was to bind the SelectedIndex of the TabControl to a property in my viewmodel.
With the index I can then "select" the related view (viewmodel) and get the name and bind it to the header (see HeaderedContentControl; Content="{Binding Path=Workspaces}"; Workspaces is of type ObservableCollection)
But once I bind the SelectedIndex property of the TabControl, the TabControl does not switch reliable anymore. Sometimes when I click on the TabItem it is switching sometimes not. Sometimes I need to click ten or more times. A very strange behavior. There is no difference if I implement the property (SelectedIndex) in my viewmodel or not.
2) Elementbinding:
My second idea was to implement a ElementName-Binding
But as I expected this does not work (Visual / Logical Tree). The error is:
"Cannot find source for binding with reference 'ElementName=TabControl'.
BindingExpression:Path=ActualHeight; DataItem=null; target element is
'HeaderedContentControl' (Name=''); target property is 'Header' (type
'Object')"
In this case I also tried to move the TabControl into the Resources of the UserControl and Grid.
So the question is: Is it possible / how is it possible to show the name of the selected tab of the TabControl in the header of the HeaderedContentControl?
Is there a solution without code behind (I don't really like code behind ;) )?
A DataTemplate inside a Resource Dictionary needs to refer to a Styles.xaml, so I have the following
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:WPFApp">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="resources/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type src:MyFileInfo}">
<Grid>
grid stuff
</Grid>
<TextBlock> stuff </TextBlock>
</DataTemplate>
</ResourceDictionary>
but there is an error at DataTemplate saying that The proprety "Visual Tree" can only be set once. What does this mean? Is it good practice to put a DataTemplate inside a ResourceDictionary? How to access other resources inside a ResourceDictionary?
A DataTemplate should only have one child. Use this:
<DataTemplate DataType="{x:Type src:MyFileInfo}">
<Grid>
grid stuff
<TextBlock> stuff </TextBlock>
</Grid>
</DataTemplate>
What is the best, most possible, proper way to store data inside elements?
I used to use a separated XML file and now i'm using the Tag and tooltip property.
It's a string-type data, e.g.:
Theme data Theme1.fg.ffffffff;Theme2.fg.ff000000;
Margins according to window size Margin.16:9.10,5,10,5;
With WPF/XAML an ideal approach could be to store such strings in Resources of the respective element or in a ResourceDictionary
eg
<Grid x:Name="myGrid" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Grid.Resources>
<sys:String x:Key="ThemeData">Theme1.fg.ffffffff;Theme2.fg.ff000000;</sys:String>
<sys:String x:Key="Margins">Margin.16:9.10,5,10,5;</sys:String>
</Grid.Resources>
</Grid>
to use the same you have two approach
xaml approach
<TextBlock Text="{StaticResource ThemeData}" />
code behind
string themeData = myGrid.FindResource("ThemeData");
these resources can also be stored in a ResourceDictionary which can further be merged in any element, window or even whole application
eg
StringResources.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="ThemeData">Theme1.fg.ffffffff;Theme2.fg.ff000000;</sys:String>
<sys:String x:Key="Margins">Margin.16:9.10,5,10,5;</sys:String>
</ResourceDictionary>
usage
<Grid x:Name="myGrid">
<Grid.Resources>
<ResourceDictionary Source="StringResources.xaml" />
</Grid.Resources>
<TextBlock Text="{StaticResource ThemeData}" />
</Grid>
or this if you want to merge/override some more resources
<Grid x:Name="myGrid">
<Grid.Resources>
<ResourceDictionary xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="StringResources.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--define new resource or even override existing for this specific element -->
<sys:String x:Key="ThemeData">Theme1.fg.ff00ff00;Theme2.fg.ff0000ff;</sys:String>
<sys:String x:Key="NewMargins">Margin.16:9.10,5,10,5;</sys:String>
</ResourceDictionary>
</Grid.Resources>
<TextBlock Text="{StaticResource ThemeData}" />
</Grid>
The way I understood, you can use Tag property on controls to store the info . it accepts object type. hence you can attach any type to it. like control.Tag = objectyouwantto attach.
if my answer seems not relevant, please elaborate your question
The main menu of my program uses a ContextMenu composed of MenuItems. During the localization of my program (using Resource Dictionaries), I set a DynamicResource as the Header of each one of my MenuItems. Strangely DynamicResource compiles, but doesn't seem to affect any change during localization (the language on the Headers does not change).
Example of a MenuItem:
//I'm not sure if the x:Name or the PlacementRectangle is interfering with anything...
<ContextMenu x:Name="MainContextMenu" PlacementRectangle="{Binding RelativeSource={RelativeSource Self}}">
<MenuItem Header="{DynamicResource open}" />
</ContextMenu>
What are the constraints of the MenuItem control? Is it supposed to work with DynamicResource? My overall goal is to localize these strings, how do I do that?
This program is in WPF. Thank you.
UPDATE:
This is how my Resource Dictionaries are referenced in my App.xaml file:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Lang.en-US.xaml" />
</ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<Application.Resources>
UPDATE 2:
The example string in my English Resource Dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:String x:Key="open">Open</sys:String>
</ResourceDictionary>
Update 3:
An example function for how I change the current Resource Dictionary to Spanish:
private void spanishChange_Click(object sender, RoutedEventArgs e)
{
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(
(ResourceDictionary)Application.LoadComponent(new Uri("LangspES.xaml", UriKind.Relative)));
LanguageChange.FireLanguageChanged();
}
Have you added LANGUAGE.xaml file to App.ResourceDictionary or control ResourceDictionary?
e.g.
<Application.Resources>
<ResourceDictionary Source="LANGUAGE1.xaml" />
<ResourceDictionary Source="LANGUAGE2.xaml" />
</Application.Resources>
If not how are you referencing your resource dictionaries?
Update:
If you change
<MenuItem Header="{DynamicResource open}" />
to
<MenuItem Header="{StaticResource open}" />
Does it then work ? Or even does
<TextBox DockPanel.Dock="Top" Text="{StaticResource open}" />
work ?
Seemingly your xaml should work, which makes me wonder have you setup localisation correctly in your app?
For how to setup localisation in .net 4.5 see this msdn link
I am using a slightly modified version of this SimpleExpander so that my expander headers are clickable.
http://www.codeproject.com/Articles/248112/Templating-WPF-Expander-Control
Here is the relevant source code:
<ControlTemplate x:Key="SimpleExpanderTemp" TargetType="{x:Type Expander}">
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<ToggleButton x:Name="ExpanderButton" />
<!-- button with hard coded click handler -->
<Button Name="SharerHeader" Click="SharerHeader_Click_1">
<ContentPresenter Content="{TemplateBinding Header}" />
</Button>
</StackPanel>
</DockPanel>
</ControlTemplate>
Works great. Now I want to reuse this control template across my application, so I moved the code into my app.xaml. However, my header is hardcoded to a click event, and I want to be able to set the click handler based on the page the expander is being used in. How can I accomplish this?
Add a new ResourceDictionary to your project (name it RD1.xaml), move your Template into it.
Add a new Class (name it RD1.xaml.cs) with this signature
public partial class SomeClassName : ResourceDictionary
Add this line of code to the ResourceDictionary tag (in RD1.xaml)
x:Class="WpfApplication1.SomeClassName"
Add these lines of code to App.xaml (add FolderName if RD1 is in a folder)
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="FolderName\RD1.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Now you can have your event handler implementation in RD1.xaml.cs