如何使这个DataTemplateSelector工作?

时间:2013-03-12 15:41:49

标签: c# wpf datatemplateselector

在我的Viewmodel中,我有类型为string的属性LoggedInAs和bool类型的EditMode。我还有一个名为ReaderList的List属性,我将其绑定到ItemsControl以进行显示,如下所示:

<ItemsControl Name="ReaderList" ItemTemplateSelector="{StaticResource drts}"/>

我正在使用Caliburn.Micro,因此命名会自动完成Binding。我想使用DataTemplateSelector,因为如果应用程序在EditMode中并且Person是登录的那个,我想要一个根本不同的显示。所以这是我对资源的声明,

<UserControl.Resources>
    <DataTemplate x:Key="OtherPersonTemplate"> ... </DataTemplate>
    <DataTemplate x:Key="CurrentUserIsPersonTemplate"> ...  </DataTemplate>

    <local:DisplayReaderTemplateSelector x:Key="drts" 
           IsLoggedInAs="{Binding LoggedInAs}" 
           IsEditMode="{Binding EditMode}" 
           CurrentUserTemplate="{StaticResource CurrentUserIsPersonTemplate}"
           OtherUserTemplate="{StaticResource OtherPersonTemplate}"/>
</UserControl.Resources>

这里是该类的代码:

public class DisplayReaderTemplateSelector: DataTemplateSelector {
    public DataTemplate CurrentUserTemplate { get; set; }
    public DataTemplate OtherUserTemplate { get; set; }

    public string IsLoggedInAs {get; set;}
    public bool IsEditMode { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container){
        var _r = item as Person;
        if (IsEditMode && _r.Name == IsLoggedInAs) return CurrentUserTemplate;
        else return OtherUserTemplate;
    }
}

由于某种原因,应用程序在实例化Viewmodel(相应的View)时崩溃。错误在哪里,和/或我怎样才能解决这个问题呢?

编辑:崩溃是由于DisplayReaderTemplateSelector构造中的绑定表达式 - 因为IsLoggedInEditMode不是DependencyProperties。

所以现在的问题是:如果我无法绑定到值,我怎么能拥有依赖于ViewModel状态的DataTemplateSelector呢?

1 个答案:

答案 0 :(得分:1)

虽然您可以使用DataTemplateSelector或类似的东西,但在Caliburn中找到它可能不会让您感到惊讶.Micro以View.Context的形式内置了此功能ViewLocator

在您的VM上,您可以创建一个属性,该属性提供CM将用于解析View的上下文字符串 - 因为它使用命名约定,您只需要为子视图提供正确的命名空间/名称以及上下文字符串,用于查找备用视图

在您的VM中,您可以创建一个上下文属性,该属性使用用户详细信息来确定其值:

public class SomeViewModel
{
    public string Context 
    {
        get 
        { 
            if (IsEditMode && _r.Name == IsLoggedInAs) return "Current";
            else return "Other";
        }
    }  

    // ... snip other code
}

我看到的唯一问题(可能有一个解决方法)是你要从ViewModel内部确定视图 - 通常你确定更高的上下文并将其传递给ContentControl并且CM在查找该VM的视图时使用它

e.g。

您的主要虚拟机:

public class MainViewModel
{
    public SomeSubViewModel { get; set; } // Obviously would be property changed notification and instantiation etc, I've just left it out for the example
}

和相关视图

<UserControl>
    <!-- Show the default view for this view model -->
    <ContentControl x:Name="SomeSubViewModel" />
    <!-- Show an alternative view for this view model -->
    <ContentControl x:Name="SomeSubViewModel" cal:View.Context="Alternative" />
</UserControl>

然后您的VM命名结构将是:

- ViewModels
|
----- SomeSubViewModel.cs
    |
    - SomeSubView.xaml
    |
    - SomeSubView
    |
    ----- Alternative.xaml

并且CM会知道根据原始VM名称和SomeSubView属性(SomeSubViewModel减去模型加点加{{1}在Alternative命名空间中查找名为Context的控件这是SomeSubView.Alternative)

所以我必须要玩,因为这是标准的做法。如果您这样做,则必须创建子视图模型并向视图添加Context并将ContentControl属性绑定到VM上的View.Context属性,或者将Context属性添加到更高的位置(到父虚拟机)。

我会看一些替代方案 - 如果没有办法让当前的ViewModel根据使用标准CM的属性来决定其视图,您可以自定义Context并可能使用接口(IProvideContext或somesuch)它立即为ViewLocator提供了一个上下文 - (我认为你不能直接从VM中查看解析过程)

我很快会回来另一个答案或替代方案!

编辑:

好的,这似乎是最简单的方法。我刚创建了一个直接从VM提供ViewLocator的接口

Context

然后我自定义public interface IProvideContext { string Context { get; } } 实现(您可以在ViewLocator中执行此操作),如果没有指定上下文,则使用此实现:

Bootstrapper.Configure()

这应该对你有用,并允许你直接在目标ViewLocator.LocateForModel = (model, displayLocation, context) => { var viewAware = model as IViewAware; // Added these 3 lines - the rest is from CM source // Try cast the model to IProvideContext var provideContext = model as IProvideContext; // Check if the cast succeeded, and if the context wasn't already set (by attached prop), if we're ok, set the context to the models context property if (provideContext != null && context == null) context = provideContext.Context; if (viewAware != null) { var view = viewAware.GetView(context) as UIElement; if (view != null) { #if !SILVERLIGHT && !WinRT var windowCheck = view as Window; if (windowCheck == null || (!windowCheck.IsLoaded && !(new WindowInteropHelper(windowCheck).Handle == IntPtr.Zero))) { LogManager.GetLog(typeof(ViewLocator)).Info("Using cached view for {0}.", model); return view; } #else LogManager.GetLog(typeof(ViewLocator)).Info("Using cached view for {0}.", model); return view; #endif } } return ViewLocator.LocateForModelType(model.GetType(), displayLocation, context); }; 上设置上下文 - 显然这可能仅适用于View-First方法

所以你需要做的就是构建你上面显示的视图(正确的命名空间等),然后根据ViewModel和{{1}的值在你的VM上设置Context属性}