开发者

Using a resource and DataTemplateSelector from a different assembly

开发者 https://www.devze.com 2023-04-05 11:48 出处:网络
I have a control in a base assembly with Content that I would like to set based on the current DataContext.

I have a control in a base assembly with Content that I would like to set based on the current DataContext.

To do so, I am trying to use a resource and subclassed DataTemplateSelector in the calling assembly as shown below. My initial hope was that the sub classed DataTemplateSelector would be called, but it isn't. Then I tried adding an entry in the Resource Dictionary of the calling assembly with the same key but the sub classed selector, but that doesn't get it done either.

Is there a way to fix the code I have to make this work? Is there a better strategy to set my content from the calling assembly?

Cheers,

Berryl

User Control (base assembly)

<UserControl 
    ...

    <Grid>
        <Border 开发者_开发知识库Style="{StaticResource FilterPanelBorderStyle}">
            <StackPanel Orientation="Horizontal" x:Name="myFilterPanel" >

      *****     <ContentControl x:Name="ctrlFilters" ContentTemplateSelector="{StaticResource filterControlsTemplateSelector}" /> ****

                <Button x:Name="btnClearFilter" Style="{StaticResource FilterPanelClearButtonStyle}" />
                <Label x:Name="lblStatus" Style="{StaticResource FilterPanelLabelStyle}" Content="{Binding Status}" />

            </StackPanel>
        </Border>

    </Grid>
</UserControl>

Resources and DataTemplateSelector (base assembly)

<views:FilterControlsTemplateSelector x:Key="filterControlsTemplateSelector"/>

<DataTemplate x:Key="defaultFilterContent">
    <TextBlock>Replace ME with real filters!</TextBlock>
</DataTemplate>

    public class FilterControlsTemplateSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var fe = container as FrameworkElement;
        if (fe == null) return null;

        return _GetDataTemplate(fe);
    }

    protected virtual DataTemplate _GetDataTemplate(FrameworkElement fe) {
        var template = fe.FindResource("defaultFilterContent") as DataTemplate;
        return template;
    }
}

Resources and Selector (calling Assembly)

<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="pack://application:,,,/Core.Presentation.Wpf;component/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>

<local:PimFilterControlsTemplateSelector x:Key="filterControlsTemplateSelector"/>

<DataTemplate x:Key="pimFilterContent">
    <Grid>
        <Border Style="{StaticResource FilterPanelBorderStyle}">
            <StackPanel Orientation="Horizontal" >
                <cc:SearchTextBox 
                        x:Name="stbLastNameFilter" Style="{StaticResource FilterPanelSearchTextBoxStyle}"
                        />
                <cc:SearchTextBox 
                        x:Name="stbFirstNameFilter" Style="{StaticResource FilterPanelSearchTextBoxStyle}"
                        />
            </StackPanel>
        </Border>

    </Grid>
</DataTemplate>


public class PimFilterControlsTemplateSelector : FilterControlsTemplateSelector
{

    protected override DataTemplate _GetDataTemplate(FrameworkElement fe)
    {
        var dc = fe.DataContext;
        if (dc == null) return null;

        DataTemplate result = null;
        if (dc is PimMasterVm)
        {
            result = fe.FindResource("pimFilterContent") as DataTemplate;
        }
        else {
            result = base._GetDataTemplate(fe);
        }
        return result;
    }

}

Application Dictionary setup (calling assembly)

    <Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/Parties.Presentation.Wpf;component/PimCommonResources.xaml" />                
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>

</Application.Resources>


I gave up on making the DataTemplateSelector work and wound up doing the following:

  1. creating a FilterContentConverter
  2. adding a FilterContentKey (string) to my view model

The converter just takes the FilterContentKey and does a resource lookup to get the DataTemplate with that key. This winds up being nicely testable, and even better - it works!

Solution code below, with thanks to Vladamir Dorokhov and this SO answer for helping me get the ContentControl binding right.

HTH,
Berryl

Filtering Control

Using a resource and DataTemplateSelector from a different assembly

<Grid>
    <Border Style="{StaticResource FilterPanelBorderStyle}">
        <StackPanel Orientation="Horizontal" x:Name="myFilterPanel" >
            <ContentControl x:Name="ctrlFilters" 
                            ContentTemplate="{Binding Path=FilterContentKey, Converter={StaticResource filterTemplateContentConv}}" />
            <Button x:Name="btnClearFilter" Style="{StaticResource FilterPanelClearButtonStyle}" />
            <Label x:Name="lblStatus" Style="{StaticResource FilterPanelLabelStyle}" Content="{Binding Status}" />

        </StackPanel>
    </Border>

</Grid>

Data Template (resource)

<DataTemplate x:Key="pimFilterContent">
    <StackPanel Orientation="Horizontal" >
        <cc:SearchTextBox x:Name="stbLastNameFilter" 
            Style="{StaticResource FilterPanelSearchTextBoxStyle}"
            Text="{Binding Path=LastNameFilter, UpdateSourceTrigger=PropertyChanged}" 
                        />
        <cc:SearchTextBox x:Name="stbFirstNameFilter" 
            Style="{StaticResource FilterPanelSearchTextBoxStyle}"
            Text="{Binding Path=FirstNameFilter, UpdateSourceTrigger=PropertyChanged}" 
                        />
    </StackPanel>
</DataTemplate>

Converter

/// <summary>
/// Thin wrapper around a resource lookup designed to result in a <see cref="DataTemplate"/> 
/// representing filering controls.
/// </summary>
[ValueConversion(typeof(object), typeof(DataTemplate))]
public class FilterTemplateContentConverter : IValueConverter
{
    public const string DEFAULT_CONTENT = "undefinedFilterContent";
    protected readonly ResourceLocator _resourceLocator;

    /// <summary>
    /// Initializes a new instance of the <see cref="FilterTemplateContentConverter"/> class.
    /// Unit tests can use this to pass in an app for the <see cref="ResourceLocator"/>.
    /// </summary>
    /// <param name="app">The app.</param>
    public FilterTemplateContentConverter(Application app) { _resourceLocator = new ResourceLocator(app); }

    /// <summary>
    /// Initializes a new instance of the <see cref="FilterTemplateContentConverter"/> class.
    /// The 'real' application uses this.
    /// </summary>
    public FilterTemplateContentConverter()
    {
        _resourceLocator = new ResourceLocator();
    }

    public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
        var key = value as string;
        return _resourceLocator.GetResource(key ?? DEFAULT_CONTENT);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号