将新项目添加到绑定的 ItemsSource 时,WinUI 3 UWP TabView 不显示新选项卡

Posted

技术标签:

【中文标题】将新项目添加到绑定的 ItemsSource 时,WinUI 3 UWP TabView 不显示新选项卡【英文标题】:WinUI 3 UWP TabView NOT displaying New Tab when a new Item is added to the bound ItemsSource 【发布时间】:2021-10-12 20:19:05 【问题描述】:

我在我的应用程序中使用 WinUI 3 UWP TabView。我知道 WinUI 3 仍处于 UWP 的预览阶段。但我仍然想知道我的问题的解决方法,因为我想在我的应用程序中使用 TabView。我浏览了官方文档和 GitHub 示例,但找不到解决方案。每当将新文档添加到集合中时,TabView 都不会显示新选项卡。我进行了很多搜索,但找不到解决方案。请分享解决方案/解决方法。您可能会建议使用 WinUI 2,因为它对于 UWP 来说是稳定的。但是,我已经尝试过,WinUI 2 控件与现有的 UWP 控件不能很好地融合。但 WinUI 3 完美融合。除 TabView 之外的所有其他控件都运行良好。当我从 DataBinding 切换到手动维护 TabItems 列表时,它工作得很好。但是,我不想要样板代码。我想用 DataBinding 达到同样的效果。我是 MVVM 的新手。所以,如果我的 ViewModel 有问题,请分享解决方法。

这是我的 ViewModel 类:

    using Microsoft.UI.Xaml.Controls;
    using System.ComponentModel;
    using System.IO;
    using System.Text;
    using MyApp.Utilities;
    using System.Runtime.CompilerServices;
    namespace MyApp.ViewModels
     
        public class TextDocument : INotifyPropertyChanged
      
        private int _documentId;
        private string _fileName;
        private string _filePath;
        private string _textContent;
        private Encoding _encoding;
        private LineEnding _lineEnding;
        private bool _isSaved;
        public int DocumentId
        
            get
            
                return _documentId;
            
            set
            
                _documentId = value;
                OnPropertyChanged(ref _documentId, value);
            
        
        public string FileName
        
            get
            
                return _fileName;
            
            set
            
                OnPropertyChanged(ref _fileName, value);
            
        

        public string FilePath
        
            get
            
                return _filePath;
            
            set
            
                OnPropertyChanged(ref _filePath, value);
                FileName = Path.GetFileName(_filePath);
            
        

        public string TextContent
        
            get
            
                return _textContent;
            
            set
            
                OnPropertyChanged(ref _textContent, value);
            
        

        public Encoding FileEncoding
        
            get
            
                return _encoding;
            
            set
            
                OnPropertyChanged(ref _encoding, value);
            
        
        public LineEnding LineEnding
        
            get
            
                return _lineEnding;
            
            set
            
                OnPropertyChanged(ref _lineEnding, value);
            
        
        public string TabHeader
        
            get
            
               return string.IsNullOrEmpty(FileName) ? "Untitled Document" : FileName;
            
        
        public bool IsSaved
        
            get
            
                return _isSaved;
            
            set
            
                OnPropertyChanged(ref _isSaved, value);
            
        
        public bool IsInvalidFile 
         
            get 
             
                return (string.IsNullOrEmpty(FilePath) || string.IsNullOrEmpty(FileName)); 
             
        
        public override bool Equals(object obj)
        
            if (ReferenceEquals(obj, null))
                return false;
            if (ReferenceEquals(this, obj))
                return true;
            var comp = (TextDocument)obj;
            return this.DocumentId == comp.DocumentId;
        
        public override int GetHashCode()
        
            return base.GetHashCode();
        
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged<T>(ref T property, T value, [CallerMemberName] string propertyName = "")
        
            property = value;
            var handler = PropertyChanged;
            if (handler != null)
            
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            
        
    
 

这是我的 TabView 的 XAML 代码:

<TabView x:Name="MyTabView" AddTabButtonClick="TabView_AddTabButtonClick" TabCloseRequested="TabView_TabCloseRequested"
             SelectionChanged="TabView_SelectionChanged"
             Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Background="White"
             HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
             TabItemsChanged="TabView_TabItemsChanged"
             SelectedIndex="0"
             TabItemsSource="x:Bind MyDocuments,Mode=OneWay"
             >
        <TabView.TabItemTemplate>
            <DataTemplate x:DataType="viewmodels:TextDocument">
                <TabViewItem Header="x:Bind TabHeader,Mode=OneWay" IconSource="x:Null">
                    <TabViewItem.Content>
                        <TextBox x:Name="TextBoxInsideTab" Grid.Column="0" Grid.Row="0" 
                                PlaceholderText="Drag and drop a file here or start typing"        
                                Text="x:Bind TextContent,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged" FontSize="24" 
                                UseSystemFocusVisuals="False"
                                BorderThickness="0"
                                VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
                                TextWrapping="Wrap"
                                IsSpellCheckEnabled="False"
                                CanBeScrollAnchor="True"
                                TextChanged="TextBox_TextChanged"
                                AcceptsReturn="True"
                                IsTabStop="True" 
                                ScrollViewer.VerticalScrollBarVisibility="Auto"
                                ScrollViewer.HorizontalScrollBarVisibility="Auto" 
                                />
                    </TabViewItem.Content>
                </TabViewItem>
            </DataTemplate>
        </TabView.TabItemTemplate>
    </TabView>

这是我的 C# 代码:

    using Microsoft.UI.Xaml;
    using Microsoft.UI.Xaml.Controls;
    using Microsoft.UI.Xaml.Media;
    using MyApp.ViewModels;
    using Windows.Storage.Pickers;
    using Windows.Storage;
    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    namespace MyApp

    public sealed partial class MainPage : Page
    
        private ObservableCollection<TextDocument> MyDocuments;
        public MainPage()
        
            this.InitializeComponent();
            MyDocuments = new ObservableCollection<TextDocument>()
            
                new TextDocument()
            ;
        
        private void TabView_AddTabButtonClick(TabView sender, object args)
        
            AddTabViewItemDefault(sender.TabItems.Count);
        
        private void AddTabViewItemDefault(int index)
        
            MyDocuments.Add(new TextDocument());
        
        private void TabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args)
        
            MyDocuments.Remove(args.Item as TextDocument);
        
        private void TabView_SelectionChanged(object sender, SelectionChangedEventArgs e)
        
           
        
        private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
        

        

    

【问题讨论】:

也许添加一个 UpdateSourceTrigger=PropertyChanged 到 TabItemsSource? 是否显示初始的TabItem? @Chris 是的,初始的 TabItem 已显示。但是,如果单击 AddTab 按钮,则不会显示任何内容。但该项目确实被添加到 ObservableCollection 中。而且,我确实尝试过 Mode=TwoWay,UpdateSourceTrigger=PropertyChanged。它也不起作用。 你在下面看到我的回答了吗?你试过了吗?对不起,我无法构建项目等。我在工作,忙于做其他事情...... 您的 ViewModel 没问题。它一定是 ObservableCollection 没有正确绑定到 TabControl 的东西。也许在类后面的代码中添加 INotifyPropertyChanged 并在添加项目后通知 ObservableCollection 更改? 【参考方案1】:

UWP 应用中的ObservableCollection&lt;T&gt;INotifyCollectionChanged currently don't work。

您需要实现自己的自定义集合作为解决方法:

using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Interop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

using NotifyCollectionChangedAction = Microsoft.UI.Xaml.Interop.NotifyCollectionChangedAction;

public class CustomObservableCollection<T> : Collection<T>, Microsoft.UI.Xaml.Interop.INotifyCollectionChanged, INotifyPropertyChanged

    private ReentrancyGuard reentrancyGuard = null;

    private class ReentrancyGuard : IDisposable
    
        private CustomObservableCollection<T> owningCollection;

        public ReentrancyGuard(CustomObservableCollection<T> owningCollection)
        
            owningCollection.CheckReentrancy();
            owningCollection.reentrancyGuard = this;
            this.owningCollection = owningCollection;
        

        public void Dispose()
        
            owningCollection.reentrancyGuard = null;
        
    

    public CustomObservableCollection() : base()  
    public CustomObservableCollection(IList<T> list) : base(list.ToList())  
    public CustomObservableCollection(IEnumerable<T> collection) : base(collection.ToList())  

    public event Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventHandler CollectionChanged;

    public void Move(int oldIndex, int newIndex)
    
        MoveItem(oldIndex, newIndex);
    

    protected IDisposable BlockReentrancy()
    
        return new ReentrancyGuard(this);
    

    protected void CheckReentrancy()
    
        if (reentrancyGuard != null)
        
            throw new InvalidOperationException("Collection cannot be modified in a collection changed handler.");
        
    

    protected override void ClearItems()
    
        CheckReentrancy();

        TestBindableVector<T> oldItems = new TestBindableVector<T>(this);

        base.ClearItems();
        OnCollectionChanged(
            NotifyCollectionChangedAction.Reset,
            null, oldItems, 0, 0);
    

    protected override void InsertItem(int index, T item)
    
        CheckReentrancy();

        TestBindableVector<T> newItem = new TestBindableVector<T>();
        newItem.Add(item);

        base.InsertItem(index, item);
        OnCollectionChanged(
            NotifyCollectionChangedAction.Add,
            newItem, null, index, 0);
    

    protected virtual void MoveItem(int oldIndex, int newIndex)
    
        CheckReentrancy();

        TestBindableVector<T> oldItem = new TestBindableVector<T>();
        oldItem.Add(this[oldIndex]);
        TestBindableVector<T> newItem = new TestBindableVector<T>(oldItem);

        T item = this[oldIndex];
        base.RemoveAt(oldIndex);
        base.InsertItem(newIndex, item);
        OnCollectionChanged(
            NotifyCollectionChangedAction.Move,
            newItem, oldItem, newIndex, oldIndex);
    

    protected override void RemoveItem(int index)
    
        CheckReentrancy();

        TestBindableVector<T> oldItem = new TestBindableVector<T>();
        oldItem.Add(this[index]);

        base.RemoveItem(index);
        OnCollectionChanged(
            NotifyCollectionChangedAction.Remove,
            null, oldItem, 0, index);
    

    protected override void SetItem(int index, T item)
    
        CheckReentrancy();

        TestBindableVector<T> oldItem = new TestBindableVector<T>();
        oldItem.Add(this[index]);
        TestBindableVector<T> newItem = new TestBindableVector<T>();
        newItem.Add(item);

        base.SetItem(index, item);
        OnCollectionChanged(
            NotifyCollectionChangedAction.Replace,
            newItem, oldItem, index, index);
    

    protected virtual void OnCollectionChanged(
        NotifyCollectionChangedAction action,
        IBindableVector newItems,
        IBindableVector oldItems,
        int newIndex,
        int oldIndex)
    
        OnCollectionChanged(new Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs(action, newItems, oldItems, newIndex, oldIndex));
    

    protected virtual void OnCollectionChanged(Microsoft.UI.Xaml.Interop.NotifyCollectionChangedEventArgs e)
    
        using (BlockReentrancy())
        
            CollectionChanged?.Invoke(this, e);
        
    

#pragma warning disable 0067 // PropertyChanged is never used, raising a warning, but it's needed to implement INotifyPropertyChanged.
    public event PropertyChangedEventHandler PropertyChanged;
#pragma warning restore 0067


public class TestBindableVector<T> : IList<T>, IBindableVector

    IList<T> implementation;

    public TestBindableVector()  implementation = new List<T>(); 
    public TestBindableVector(IList<T> list)  implementation = new List<T>(list); 

    public T this[int index]  get => implementation[index]; set => implementation[index] = value; 

    public int Count => implementation.Count;

    public virtual bool IsReadOnly => implementation.IsReadOnly;

    public void Add(T item)
    
        implementation.Add(item);
    

    public void Clear()
    
        implementation.Clear();
    

    public bool Contains(T item)
    
        return implementation.Contains(item);
    

    public void CopyTo(T[] array, int arrayIndex)
    
        implementation.CopyTo(array, arrayIndex);
    

    public IEnumerator<T> GetEnumerator()
    
        return implementation.GetEnumerator();
    

    public int IndexOf(T item)
    
        return implementation.IndexOf(item);
    

    public void Insert(int index, T item)
    
        implementation.Insert(index, item);
    

    public bool Remove(T item)
    
        return implementation.Remove(item);
    

    public void RemoveAt(int index)
    
        implementation.RemoveAt(index);
    

    IEnumerator IEnumerable.GetEnumerator()
    
        return implementation.GetEnumerator();
    

    public object GetAt(uint index)
    
        return implementation[(int)index];
    

    public IBindableVectorView GetView()
    
        return new TestBindableVectorView<T>(implementation);
    

    public bool IndexOf(object value, out uint index)
    
        int indexOf = implementation.IndexOf((T)value);

        if (indexOf >= 0)
        
            index = (uint)indexOf;
            return true;
        
        else
        
            index = 0;
            return false;
        
    

    public void SetAt(uint index, object value)
    
        implementation[(int)index] = (T)value;
    

    public void InsertAt(uint index, object value)
    
        implementation.Insert((int)index, (T)value);
    

    public void RemoveAt(uint index)
    
        implementation.RemoveAt((int)index);
    

    public void Append(object value)
    
        implementation.Add((T)value);
    

    public void RemoveAtEnd()
    
        implementation.RemoveAt(implementation.Count - 1);
    

    public uint Size => (uint)implementation.Count;

    public IBindableIterator First()
    
        return new TestBindableIterator<T>(implementation);
    


public class TestBindableVectorView<T> : TestBindableVector<T>, IBindableVectorView

    public TestBindableVectorView(IList<T> list) : base(list)  

    public override bool IsReadOnly => true;


public class TestBindableIterator<T> : IBindableIterator

    private readonly IEnumerator<T> enumerator;

    public TestBindableIterator(IEnumerable<T> enumerable)  enumerator = enumerable.GetEnumerator(); 

    public bool MoveNext()
    
        return enumerator.MoveNext();
    

    public object Current => enumerator.Current;

    public bool HasCurrent => enumerator.Current != null;

页面

public sealed partial class MainPage : Page

    private CustomObservableCollection<TextDocument> MyDocuments;
    public MainPage()
    
        this.InitializeComponent();
        MyDocuments = new CustomObservableCollection<TextDocument>()
        
            new TextDocument()
        ;
    
    ...

【讨论】:

但是 ObservableCollection 和 TabView 与 WinUI 3 桌面应用程序一起正常工作。这个问题似乎只存在于 WinUI 3 UWP Library 中。 严重代码描述项目文件行抑制状态错误 CS1503 参数 1:无法从 'TypeX_Notepad_WinUI_3_UWP.CustomObservableCollection' 转换为 'System.Collections.ObjectModel.ObservableCollection' TypeX Notepad WinUI 3 UWP C:\Users\arunp\source\repos\TypeX Notepad WinUI 3 UWP\TypeX Notepad WinUI 3 UWP\obj\x86\Debug\MainPage.g.cs 1024 Active 在构建应用程序时,我在 MainPage.g.cs 中收到以下错误。这是发生错误的方法: private void Update_(global::TypeX_Notepad_WinUI_3_UWP.MainPage obj, int phase) if (obj != null) if ((phase & (NOT_PHASED | DATA_CHANGED | (1 是的,该问题仅在使用 UWP 应用模型时存在,正如我发布的链接所解释的那样。你是不是到处都改成CustomObservableCollection&lt;TextDocument&gt;了。它对我有用。 我在 MainPage.g.cs 中的 this.Update_MyDocuments() 中遇到错误。如果以某种方式消除了该错误,一切都会正常工作。【参考方案2】:

我认为您在构造函数中的代码可能会破坏与 ObservableCollection 的初始绑定。试试这个代码:

private ObservableCollection<TextDocument> MyDocuments get; = new ObservableCollection<TextDocument>();

public MainPage()

    this.InitializeComponent();
    MyDocuments.Add(new TextDocument());

有帮助吗?

【讨论】:

我会试试这个并回复你 不幸的是这个解决方案也没有解决问题:-(

以上是关于将新项目添加到绑定的 ItemsSource 时,WinUI 3 UWP TabView 不显示新选项卡的主要内容,如果未能解决你的问题,请参考以下文章

将 UWP ComboBox ItemsSource 绑定到 Enum

如何将 ComboBox 的 SelectedItem 绑定到作为 ItemsSource 中项目副本的对象?

将 UserControl ListBox ItemSsource 绑定到父 DataContext 时出错

如何将命令绑定添加到动态按钮?

为啥更改 ItemsSource 时 DataGrid 不更新?

WPF ComboBox 绑定 ItemsSource