how to access other resources inside a ResourceDictionary - c#

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>

Related

How to reference a DataTemplate inside a DataTemplate?

I have this ResourceDictionary:
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<ListView>
<ListView.ItemTemplate>
<DataTemplate /> <----- Here I'd like to load an external DataTemplate from the same folder
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
In the same folder I have another ResourceDictionary:
<DataTemplate DataType="{x:Type vm:EditRecordViewModel}">
<StackPanel>
<TextBlock Text="Test" />
</StackPanel>
</DataTemplate>
Question
In my first ResourceDictionary how do I get the 2nd ResourceDictionary to display in the first, where I have the <DataTemplate /> displayed?
I have added resources in the App.xaml like so, but how do actually use them?:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Views/MainViewModel.xaml" />
<ResourceDictionary Source="Views/EditRecordViewModel.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
You can provide a key to a data template
<DataTemplate x:Key="test" DataType="{x:Type local:EditRecordViewModel}">...
and then reference it
<ListView ItemTemplate="{StaticResource test}">...
You have declared Data Templates without a key, which means that they will be applied to the corresponding types by default.
You don't need any links for this.
The only thing that matters is that the ListView gets a collection of items of type EditRecordViewModel.
Example.
TwoDataTemplte/ViewModels.cs:
namespace TwoDataTemplte.ViewModel
{
public class EditRecordViewModel
{
public string Text { set; get; }
}
public class MainViewModel
{
public EditRecordViewModel[] EditRecords { get; } =
{
new EditRecordViewModel() {Text = "First"},
new EditRecordViewModel() {Text = "Second"}
};
}
}
TwoDataTemplte\MainDictionary.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TwoDataTemplte.ViewModel">
<DataTemplate DataType="{x:Type vm:MainViewModel}">
<ListView ItemsSource="{Binding EditRecords}"/>
</DataTemplate>
</ResourceDictionary>
TwoDataTemplte\ItemDictionary.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TwoDataTemplte.ViewModel">
<DataTemplate DataType="{x:Type vm:EditRecordViewModel}">
<StackPanel>
<TextBlock Text="{Binding Text}" />
</StackPanel>
</DataTemplate>
</ResourceDictionary>
App.xaml:
<Application x:Class="Febr20y.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Febr20y"
StartupUri="TwoDataTemplte/ExampleWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="TwoDataTemplte/MainDictionary.xaml" />
<ResourceDictionary Source="TwoDataTemplte/ItemDictionary.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
TwoDataTemplte\ExampleWindow.xaml:
<Window x:Class="TwoDataTemplte.ExampleWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TwoDataTemplte"
xmlns:vm="clr-namespace:TwoDataTemplte.ViewModel"
mc:Ignorable="d"
Title="ExampleWindow" Height="450" Width="800">
<Grid>
<ContentControl>
<vm:MainViewModel/>
</ContentControl>
</Grid>
</Window>

How to reference BindingProxy instance from ResourceDictionary

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>

C# / WPF: Binding TabControl - TabItem (Name) to HeaderedContentControl - Header

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 ;) )?

Best way for storing data-string relative to elements

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

Contentcontrol doesn't find my datatemplate it's ViewModel

I'm using WPF, MVVM & PRISM.
I got a datatemplate in my View linked to a ViewModel UC2002_RFPBeheren_ViewModel cause the page were this code is included is linked to another ViewModel and I want the Button to have UC2002_RFPBeheren_ViewModel as ViewModel.
The datacontext of this page is UC2002_RFPBeheren_ProjectInfo_ViewModel but I want the SaveButton to use the ViewModel UC2002_RFPBeheren_ViewModel
Here is my code:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/RFPModuleResources.xaml" />
<ResourceDictionary>
<DataTemplate x:Key="SaveButton" DataType="{x:Type vm:UC2002_RFPBeheren_ViewModel}">
<Button Command="{Binding SaveRFPCommand}">Save</Button>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<ContentControl ContentTemplate="{StaticResource SaveButton}"/>
<Button Command="{Binding CloseTabCommand}">Close</Button>
</StackPanel>
Although the SaveButton displays but don't reacts on my command.
Do I forget something or is there another way to solve this?
Thanks in advance ;) !
=================================================================================
EDIT:
So I made some changes but it still doesn't work.
Code example:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/RFPModuleResources.xaml" />
<ResourceDictionary>
<DataTemplate x:Key="SaveButton" DataType="{x:Type vm:UC2002_RFPBeheren_ViewModel}">
<Button Command="{Binding SaveRFPCommand}">Save</Button>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
I set this property in the ViewModel of the page
public UC2002_RFPBeheren_ViewModel MySaveVM { get; set; }
My stackpanel looks now like this:
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<ContentControl Content="{Binding MySaveVM}" ContentTemplate="{StaticResource SaveButton}"/>
<Button Command="{Binding CloseTabCommand}">Close</Button>
</StackPanel>
what happens if you set your UV2002_RFPBeheren_ViewModel instance as the content for the ContentPresenter?
<ContentControl Content="{Binding MyUV2002_RFPBeheren_ViewModel}"/>
the DataTemplate just say how your Viewmodel should be displayed, but you have to set the DataContext or Binding to the instance of your viewmodel.
EDIT:
example
public class VMFoo
{
public UV2002_RFPBeheren_ViewModel MySaveVM {get; set;}
}
xaml
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/RFPModuleResources.xaml" />
<ResourceDictionary>
<DataTemplate x:Key="SaveButton" DataType="{x:Type vm:UC2002_RFPBeheren_ViewModel}">
<Button Command="{Binding SaveRFPCommand}">Save</Button>
</DataTemplate>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.DataContext>
<x:local VMFoo/>
</UserControl.DataContext>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<ContentControl Content="{Binding MySaveVM}"/>
<Button Command="{Binding CloseTabCommand}">Close</Button>
</StackPanel>
EDIT: Small working sample
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type WpfApplication1:UV2002_RFPBeheren_ViewModel}">
<Button Command="{Binding SaveRFPCommand}">Save</Button>
</DataTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Grid.DataContext>
<WpfApplication1:VMFoo/>
</Grid.DataContext>
<StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
<ContentControl Content="{Binding MySaveVM}"/>
<Button Command="{Binding CloseTabCommand}">Close</Button>
</StackPanel>
</Grid>
</Window>
Viewmodels
public class VMFoo
{
public VMFoo()
{
this.MySaveVM = new UV2002_RFPBeheren_ViewModel();
}
public UV2002_RFPBeheren_ViewModel MySaveVM { get; set; }
}
public class UV2002_RFPBeheren_ViewModel
{
private DelegateCommand _save;
public ICommand SaveRFPCommand
{
get{if(this._save==null)
{
this._save = new DelegateCommand(()=>MessageBox.Show("success"),()=>true);
}
return this._save;
}
}
}
This is because of the way that ContentControl's work. It is assumed that the things in the ContentTemplate are related to the Content and so the DataContext is set to the Content and therefore that is the DataContext that the button within the template has access to. You haven't specified a Content so the value is null and so the DataContext is explicitly set to null. You can see this in a basic example. One thing you can do is to bind the Content of the ContentControl to the DataContext - see the last contentcontrol in the example.
<StackPanel DataContext="Foo">
<StackPanel.Resources>
<DataTemplate x:Key="withBtn">
<Button Content="{Binding}" />
</DataTemplate>
</StackPanel.Resources>
<Button Content="{Binding}" />
<ContentControl ContentTemplate="{StaticResource withBtn}" />
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource withBtn}" />
</StackPanel>
If you are using MVVM you must expose some instance of UC2002_RFPBeheren_ViewModel within your UC2002_RFPBeheren_ProjectInfo_ViewModel to bind against. Either as a property, or an item of a collection that is a property.
Everything in the view must be ultimately accessible from the ViewModel that is your data context (UC2002_RFPBeheren_ProjectInfo_ViewModel)

Categories

Resources