【问题标题】:DataTemplate problems at runtime with TabControl使用 TabControl 在运行时出现 DataTemplate 问题
【发布时间】:2016-02-02 13:05:16
【问题描述】:

我正在尝试使用视图模型优先的方法,并且我已经为我的自定义图表控件创建了一个视图模型。现在,在我的表单中,我想要一个TabControl,它将显示一个 XAML 定义的图表列表,定义如下:

<coll:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
    <VM:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today" ShortName="Today"/>
    <VM:MyChartViewModel x:Name="ChartVM_Week" ChartType="Week" ShortName="This Week"/>
    <VM:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month" ShortName="This Month"/>
    <VM:MyChartViewModel x:Name="ChartVM_Qtr" ChartType="Quarter" ShortName="This Quarter"/>
    <VM:MyChartViewModel x:Name="ChartVM_Year" ChartType="Year" ShortName="This Year"/>
    <VM:MyChartViewModel x:Name="ChartVM_Cust" ChartType="Custom" ShortName="Custom"/>
</coll:ArrayList>

尝试为我的选项卡标题和内容指定数据模板,我有这个:

<DataTemplate x:Key="tab_header">
    <TextBlock Text="{Binding ShortName}" FontSize="16" />
</DataTemplate>
<DataTemplate x:Key="tab_content" DataType="{x:Type VM:MyChartViewModel}" >
    <local:MyChartControl/>
</DataTemplate>

我的TabControl是这样的:

<TabControl ItemsSource="{StaticResource ChartListTabs}"
            ItemTemplate="{StaticResource tab_header}"
            ContentTemplate="{StaticResource tab_content}" 
            IsSynchronizedWithCurrentItem="True">
    <!-- nothing here :) -->
</TabControl>

发生的情况是设计器正确显示选项卡,第一个选项卡内容(无法切换选项卡,因为它们是动态创建的)显然显示第一个图表的正确视图,但是当我运行应用程序时,所有选项卡都显示相同的、默认的、未初始化的内容(即没有设置任何属性的相同图表控件)。此外,该实例似乎是相同的,即更改我的自定义控件(例如日期框)上的某些内容,这会显示在所有选项卡上。

在我看来,TabControl 内容中的控件(视图)保持不变(TabControl 这样做,正如我在其他地方读到的那样)并且应该只在选项卡更改时更改 DataContext,但它显然没有。


注意事项:

  • 我所有的课程都是DependencyObjects,我的收藏是ObservableCollections(ChartListTabs 资源除外)
  • ShortName 是我想要作为选项卡标题文本的视图模型属性
  • This question 似乎相关,但我无法连接点

【问题讨论】:

  • 你能告诉我你的 MyChartControl 里面有什么吗?
  • 是基于 MyChartViewModel 还是有自己的数据上下文?
  • @Ilan 我认为MyChartViewModel 显然是MyChartControlDataContext。这就是 MVVM 的工作方式,不是吗?
  • @Ilan 我想我知道你的意思。你看不到`'。两者之间没有任何关系。我将编辑代码以显示它。感谢您提出这个问题(有意或无意)。
  • 内部选项卡项控件(MyChartControl)是否绑定到支持的DataContext的任何属性?

标签: wpf xaml templates tabcontrol


【解决方案1】:

这是我的解决方案,里面使用了你的代码,请尝试检查一下。

Xaml

<Window x:Class="TabControTemplatingHelpAttempt.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:collections="clr-namespace:System.Collections;assembly=mscorlib"
    xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt"
    Title="MainWindow" Height="350" Width="525">
<Window.Resources>
    <collections:ArrayList x:Key="ChartListTabs" x:Name="ChartList">
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Today" ChartType="Today"   ShortName="Today"/>
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Week" ChartType= "Week"    ShortName="This Week"/>
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Month" ChartType="Month"   ShortName="This Month"/>
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Qtr" ChartType=  "Quarter" ShortName="This Quarter"/>
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Year" ChartType= "Year"    ShortName="This Year"/>
        <tabControTemplatingHelpAttempt:MyChartViewModel x:Name="ChartVM_Cust" ChartType= "Custom"  ShortName="Custom"/>
    </collections:ArrayList>
    <DataTemplate x:Key="TabHeader" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
        <TextBlock Text="{Binding ShortName}" FontSize="16" />
    </DataTemplate>
    <DataTemplate x:Key="TabContent" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}" >
        <tabControTemplatingHelpAttempt:MyChartControl Tag="{Binding ChartType}"/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <TabControl ItemsSource="{StaticResource ChartListTabs}"
        ItemTemplate="{StaticResource TabHeader}"
        ContentTemplate="{StaticResource TabContent}" 
        IsSynchronizedWithCurrentItem="True"/>
</Grid></Window>

转换器代码

    public class ChartType2BrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var key = (ChartType) value;
        SolidColorBrush brush;
        switch (key)
        {
            case ChartType.Today:
                brush = Brushes.Tomato;
                break;
            case ChartType.Week:
                brush = Brushes.GreenYellow;
                break;
            case ChartType.Month:
                brush = Brushes.Firebrick;
                break;
            case ChartType.Quarter:
                brush = Brushes.Goldenrod;
                break;
            case ChartType.Year:
                brush = Brushes.Teal;
                break;
            case ChartType.Custom:
                brush = Brushes.Blue;
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }
        return brush;
    }

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

主虚拟机

public class MyChartViewModel:BaseObservableDependencyObject
{
    private ChartType _chartType;
    private string _shortName;

    public ChartType ChartType
    {
        get { return _chartType; }
        set
        {
            _chartType = value;
            OnPropertyChanged();
        }
    }

    public string ShortName
    {
        get { return _shortName; }
        set
        {
            _shortName = value;
            OnPropertyChanged();
        }
    }
}

public enum ChartType
{
    Today,
    Week,  
    Month,  
    Quarter,
    Year,  
    Custom,
}

内部用户控制 XAML

<UserControl x:Class="TabControTemplatingHelpAttempt.MyChartControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabControTemplatingHelpAttempt="clr-namespace:TabControTemplatingHelpAttempt">
<UserControl.Resources>
    <tabControTemplatingHelpAttempt:ChartType2BrushConverter x:Key="ChartType2BrushConverterKey" />
    <DataTemplate x:Key="UserContentTemplateKey" DataType="{x:Type tabControTemplatingHelpAttempt:MyChartViewModel}">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Rectangle Grid.Row="0" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                       Fill="{Binding ChartType, Converter={StaticResource ChartType2BrushConverterKey}}"/>
            <TextBlock Grid.Row="0" Text="{Binding ShortName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            <Grid Grid.Row="1" Tag="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.ChartType, UpdateSourceTrigger=PropertyChanged}">
                <Grid.DataContext>
                    <tabControTemplatingHelpAttempt:TabContentDataContext/>
                </Grid.DataContext>
                <Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                   Fill="{Binding BackgroundBrush}"/>
                <TextBlock Text="{Binding Description, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
            </Grid>
        </Grid>
    </DataTemplate>
</UserControl.Resources>
<Grid>
    <ContentControl Content="{Binding }" ContentTemplate="{StaticResource UserContentTemplateKey}"/>
    <!--<Grid.DataContext>
        <tabControTemplatingHelpAttempt:TabContentDataContext/>
    </Grid.DataContext>
    <Rectangle VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                       Fill="{Binding BackgroundBrush}"/>
    <TextBlock Text="{Binding Code, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" VerticalAlignment="Center" HorizontalAlignment="Center"/>-->
</Grid>

请注意,如果您注释掉 Grid.DataContext 标记并在 ContentControl 标记中注释,您的内部内容将不会更新,因为它不是根据交付的 MyChartViewModel 创建的。别处 我看不出您的代码有任何问题。

内部用户控制虚拟机

public class TabContentDataContext:BaseObservableObject
{
    private string _code;
    private Brush _backgroundBrush;


    public TabContentDataContext()
    {
        Init();
    }

    private void Init()
    {
        var code = GetCode();
        Code = code.ToString();
        BackgroundBrush = code%2 == 0 ? Brushes.Red : Brushes.Blue;
    }

    public virtual int GetCode()
    {
        return GetHashCode();
    }

    public string Code
    {
        get { return _code; }
        set
        {
            _code = value;
            OnPropertyChanged();
        }
    }


    public Brush BackgroundBrush
    {
        get { return _backgroundBrush; }
        set
        {
            _backgroundBrush = value;
            OnPropertyChanged();
        }
    }
}

可观察的目标代码

    /// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }
}

更新

基础可观察依赖对象代码

    /// <summary>
///  dependency object that implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableDependencyObject : DependencyObject, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
    {
        var propName = ((MemberExpression)raiser.Body).Member.Name;
        OnPropertyChanged(propName);
    }

    protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            OnPropertyChanged(name);
            return true;
        }
        return false;
    }
}

问候。

【讨论】:

  • 我认为BaseObservableDependencyObject 代码丢失了
  • 它有效。我正在慢慢将其靠近我的代码,以查看问题出现的时间/地点。如果您有任何建议,我很乐意尝试。
  • @Doc 我认为数据绑定已损坏,如果为您的集合(ChartListTabs)中的每个视图模型项构造相同的对象,这就是为什么我问您是否有一些绑定表达式错误在您的输出窗口中。有吗?
  • 我在容器或TabControl 中看不到任何绑定问题。 MyChartControl 内部可能存在一些问题。奇怪的是,在设计器中TabControl 看起来是正确的,但在运行时情况不同。
  • 找到了!仍然需要进行一些研究,但 TL;DR 是我在 XAML 中的 MyChartControl 中声明了一个 DataContext(我这样是为了在 XAML 中完成代码并在设计器中进行一些“预览”),它可以'不会被绑定机制覆盖。欢迎任何指点。
【解决方案2】:

在测试 Ilan 的答案时,我发现当在控件内声明 DataContext 时(即通过 UserControl.DataContext 标记作为某个类的实例),它会在控件上施加特定的对象实例,并且能够数据绑定到它的一些其他对象丢失(可能是因为 WPF 运行时使用SetData 而不是SetCurrentData)。

在设计器中“测试”控件的推荐方法是d:DataContext 声明(仅适用于设计器)。

【讨论】:

    猜你喜欢
    • 2012-09-15
    • 2012-12-06
    • 1970-01-01
    • 2014-01-11
    • 2021-06-14
    • 1970-01-01
    • 2022-01-22
    • 1970-01-01
    • 2021-11-05
    相关资源
    最近更新 更多