.net 将TabControl ItemsSource绑定到ViewModels的ObservableCollection会导致内容在焦点上刷新

.net 将TabControl ItemsSource绑定到ViewModels的ObservableCollection会导致内容在焦点上刷新,.net,wpf,mvvm,tabcontrol,observablecollection,.net,Wpf,Mvvm,Tabcontrol,Observablecollection,我正在使用MVVM框架创建一个WPF应用程序,并采用了Josh Smith关于MVVM的文章中的几个特性 最重要的是,我将TabControl绑定到ViewModels的ObservableCollection。这意味着我正在使用一个选项卡式MDI接口,该接口将UserControl显示为选项卡项的内容。我在应用程序中看到的问题是,当我有几个选项卡时,我在选项卡之间来回切换,每次我更改选项卡时,内容都会被引用 如果你下载Josh Smith的源代码,你会发现他的应用程序也有同样的问题。例如,单击

我正在使用MVVM框架创建一个WPF应用程序,并采用了Josh Smith关于MVVM的文章中的几个特性

最重要的是,我将TabControl绑定到ViewModels的ObservableCollection。这意味着我正在使用一个选项卡式MDI接口,该接口将UserControl显示为选项卡项的内容。我在应用程序中看到的问题是,当我有几个选项卡时,我在选项卡之间来回切换,每次我更改选项卡时,内容都会被引用

如果你下载Josh Smith的源代码,你会发现他的应用程序也有同样的问题。例如,单击“查看所有客户”按钮并向下滚动到列表视图的底部。接下来单击“创建新客户”按钮。当您切换回All Customer视图时,您会注意到ListView会卷回顶部。如果您切换回“新客户”选项卡并将光标放在其中一个文本框中,然后切换到“所有客户”选项卡并返回,您将注意到光标现在不见了

我想这是因为我使用了一个可观察的集合,但我不能确定。是否有任何方法可以防止在收到焦点时刷新选项卡的内容

编辑: 我在应用程序上运行探查器时发现了问题。我正在为我的ViewModels定义一个DataTemplate,以便它知道如何在ViewModel显示在选项卡中时渲染它。。。像这样:

<DataTemplate DataType="{x:Type vm:CustomerViewModel}">
    <vw:CustomerView/>
</DataTemplate>


因此,每当我切换到其他选项卡时,它都必须再次重新创建ViewModel。我通过将ViewModels的ObservableCollection更改为UserControls的ObservableCollection暂时修复了它。但是,如果可能的话,我仍然希望使用DataTemplates。有没有办法让DataTemplate正常工作?

我相信这与绑定模式有关-您尝试过{binding mode=OneWay}吗? 同时绑定到任何“数据源”对象也会导致相同的问题


我不确定单向绑定是否是您想要的行为,但我将从这里开始。

它与ObservableCollection无关。因为重用了客户的视图,WPF为每个客户创建了一个新的视图


您可以看看的Writer示例应用程序。它还为选项卡式MDI接口实现了TabControl,但不会遇到您在文章中提到的问题。在Writer中,可以通过为每个文档“选项卡”创建自己的UserControl来解决此问题。

WPF的默认行为是卸载不可见的项,包括卸载不可见的TabItems。这意味着当您返回到选项卡时,TabItem将被重新加载,任何未绑定的内容(例如滚动位置)都将被重置

有一个很好的站点,它包含扩展TabControl的代码,并阻止它在切换tab时破坏TabItems,但是现在它似乎已经不存在了

下面是我用来防止这个问题的代码。它最初来自那个网站,尽管我对它做了一些修改。切换选项卡时,它保留TabItems的
ContentPresenter
,并在返回页面时使用它重新绘制TabItem。它占用了更多的内存,但是我发现它的性能更好,因为TabItem不再需要重新创建它上面的所有控件

//扩展选项卡控件,用于保存显示的项目,这样您就不会获得
//切换选项卡时卸载和重新加载VisualTree
//从http://eric.burke.name/dotnetmania/2009/04/26/22.09.28
//并进行了一些修改,以便在执行拖放操作时重用TabItem的ContentPresenter
[TemplatePart(Name=“PART_ItemsHolder”,Type=typeof(Panel))]
公共类TabControlEx:System.Windows.Controls.TabControl
{
//保存所有项目,但仅将当前选项卡的项目标记为可见
专用面板_itemsHolder=null;
//临时保留已删除的项目,以防这是一个拖放操作
私有对象_deletedObject=null;
公共TabControlEx()
:base()
{
//这是必要的,以便我们获得初始数据绑定的选定项
this.ItemContainerGenerator.StatusChanged+=ItemContainerGenerator\u StatusChanged;
}
/// 
///如果容器已完成,则生成所选项目
/// 
/// 
/// 
void ItemContainerGenerator_状态已更改(对象发送者,事件参数e)
{
if(this.ItemContainerGenerator.Status==GeneratorStatus.ContainerGenerated)
{
this.ItemContainerGenerator.StatusChanged-=ItemContainerGenerator\u StatusChanged;
UpdateSelectedItem();
}
}
/// 
///获取ItemsHolder并生成所有子项
/// 
应用程序模板()上的公共重写无效
{
base.OnApplyTemplate();
_itemsHolder=GetTemplateChild(“PART_itemsHolder”)作为面板;
UpdateSelectedItem();
}
/// 
///当项目更改时,我们将删除所有生成的面板子项,并根据需要添加任何新的子项
/// 
/// 
已更改受保护的覆盖(NotifyCollectionChangedEventArgs e)
{
碱基(e);
如果(_itemsHolder==null)
{
返回;
}
开关(电动)
{
案例通知CollectionChangedAction.Reset:
_itemsHolder.Children.Clear();
如果(base.Items.Count>0)
{
base.SelectedItem=base.Items[0];
UpdateSelectedItem();
}
打破
案例NotifyCollectionChangedAction。添加:
案例NotifyCollectionChangedAction。删除:
//搜索由拖放操作导致的最近删除的项目
如果(如NewItems!=null&&u delet
<Style x:Key="TabControlEx_NoHeadersStyle" TargetType="{x:Type local:TabControlEx}">
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type localControls:TabControlEx}">
                <DockPanel>
                    <!-- This is needed to draw TabControls with Bound items -->
                    <StackPanel IsItemsHost="True" Height="0" Width="0" />
                    <Grid x:Name="PART_ItemsHolder" />
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>