Loading resources from ResouceDictionary by key with x:Shared="True"

1.2k views Asked by At

In WPF library I have a resource dictionary file with some templates, which need to be found by key and used as content of some control:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                xmlns:converters="clr-namespace:DbEditor.Converters"
                x:ClassModifier="internal"
                x:Class="DbEditor.Components.ScalarHandlers"
                >
<Grid x:Key="stringEditor" x:Shared="False">
    <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"  IsEnabled="{Binding IsReadOnly}"></TextBox>
</Grid>

<Grid x:Key="intEditor" x:Shared="False">
    <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="IntTextBox_PreviewTextInput" IsEnabled="{Binding IsReadOnly}"></TextBox>
</Grid>
...
</ResourceDictionary>

I find one of templates by its key in this way:

var scalarDictionary = new ResourceDictionary();
scalarDictionary.Source = new Uri("/DbEditor;component/Components/Scalar.xaml", UriKind.RelativeOrAbsolute);
var pair = scalarDictionary.OfType<DictionaryEntry>().FirstOrDefault(x => (string)x.Key == key);
return pair.Value as FrameworkElement;

But added once to some control the element can't be used in another place. In past these templates were stored in App.xaml resources and had attribute x:Shared="False" which made available to use them many times. Now adding x:Shared attribute leads to runtime error

"Shared attribute in namespace 'http://schemas.microsoft.com/winfx/2006/xaml' can be used only in compiled resource dictionaries."

I have not found any way to make the resource dictionary be compiled. Changing build action on .xaml file from Page to Resources, Embedded Resources or Compile does not change any.

Is there way to load controls from ResourceDictionary at runtime by key and use them more then once?

2

There are 2 answers

5
FLCL On BEST ANSWER

The problem was solved in a kind of inelegant way. I just created a page, moved resources there and used it in place of ResourceDictionary:

<Page x:Class="DbEditor.Components.ScalarPage" ...>
    <Page.Resources>
        <Grid x:Key="stringEditor" x:Shared="False">
            <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"  IsEnabled="{Binding IsReadOnly}"></TextBox>
        </Grid>

        <Grid x:Key="intEditor" x:Shared="False">
            <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" PreviewTextInput="IntTextBox_PreviewTextInput" IsEnabled="{Binding IsReadOnly}"></TextBox>
        </Grid>

    ...

    </Page.Resources>    
</Page>

and resouce loading:

scalarPage = new ScalarPage();               
return scalarPage.FindResource(key) as FrameworkElement;
1
ndonohoe On

You could instead use DataTemplates in your resource dictionary and set them on the containing controls ContentTemplate property instead of the Content property as I assume you are currently doing so.

You will need to change the code that finds the keys to expect a data template but this will mean you don't need to declare x:shared.

ex

    <DataTemplate x:Key="stringEditor">
       <Grid>
           <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"  IsEnabled="{Binding IsReadOnly}"></TextBox>
       </Grid>
    </DataTemplate>

<ContentControl ContentTemplate="{StaticResource stringEditor}"/>