WinUI 3 UWP TabView НЕ отображает новую вкладку, когда новый элемент добавляется в связанный ItemsSource

avatar
Kris
9 августа 2021 в 04:48
863
2
2

Я использую 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));
            }
        }
    }
 }

А это мой код XAML для TabView:

<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)
        {

        }

    }
}
Источник
Chris
9 августа 2021 в 06:32
1

Может быть, добавить UpdateSourceTrigger=PropertyChanged в TabItemsSource?

Chris
9 августа 2021 в 06:35
1

Отображается ли исходный TabItem?

Kris
9 августа 2021 в 06:38
0

@Chris Да, начальный TabItem отображается. Но если нажать кнопку AddTab, ничего не появится. Но элемент добавляется в ObservableCollection. И я попробовал Mode=TwoWay,UpdateSourceTrigger=PropertyChanged. Это тоже не работает.

Kris
9 августа 2021 в 07:01
0

@Chris Я думаю, что новые вкладки перекрывают старые. Потому что в ObservableCollection происходит изменение всякий раз, когда добавляется или удаляется новый элемент. Он не отображается в пользовательском интерфейсе. Можете ли вы собрать проект с кодом, который я отправил, и найти решение проблемы?

Chris
9 августа 2021 в 07:13
1

Вы видели мой ответ ниже? Ты это пробовал? Извините, я не смогу построить проект и т. д. Я на работе и занят другими делами...

Chris
9 августа 2021 в 07:24
1

Ваша ViewModel в порядке. Должно быть что-то с ObservableCollection, неправильно привязанным к TabControl. Может быть, добавить INotifyPropertyChanged в код класса и уведомить об изменении ObservableCollection после добавления элемента?

Kris
9 августа 2021 в 07:29
0

@ Крис Нет проблем. Спасибо за помощь. Я попробую ваше решение и свяжусь с вами.

Chris
9 августа 2021 в 09:22
0

Вы пробовали это: может быть, добавить INotifyPropertyChanged в код класса и уведомить об изменении ObservableCollection после добавления элемента?

Kris
9 августа 2021 в 11:30
0

@Chris Да, я попробовал. Тоже не получилось

Kris
10 августа 2021 в 03:31
0

@Chris ObservableCollection и TabView правильно работают с настольным приложением WinUI 3. Проблема существует только в библиотеке WinUI 3 UWP.

Ответы (2)

avatar
mm8
9 августа 2021 в 19:19
1

ObservableCollection<T> и INotifyCollectionChanged в настоящее время не работают в приложениях UWP.

В качестве временного решения необходимо реализовать собственную пользовательскую коллекцию:

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()
        };
    }
    ...
}
Kris
10 августа 2021 в 03:30
0

Но ObservableCollection и TabView правильно работают с настольным приложением WinUI 3. Проблема существует только в библиотеке WinUI 3 UWP.

Kris
10 августа 2021 в 04:02
0

Код серьезности Описание Ошибка состояния подавления строки файла проекта CS1503 Аргумент 1: невозможно преобразовать из TypeX_Notepad_WinUI_3_UWP.CustomObservableCollection<TypeX_Notepad_WinUI_3_UWP.Models.TextDocument> в System.Collections.ObjectModel.ObservableCollection<TypeX_Notepad_WinUI_3_UWP.Models. C:\Users\arunp\source\repos\TypeX Notepad WinUI 3 UWP\TypeX Notepad WinUI 3 UWP\obj\x86\Debug\MainPage.g.cs 1024 Active

Kris
10 августа 2021 в 04:04
0

Я получаю следующую ошибку в 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 << 0)) ) != 0) { this.Update_MyDocuments(obj.MyDocuments, фаза); } } }

mm8
10 августа 2021 в 13:39
1

Да, проблема существует только при использовании модели приложения UWP, как объясняется в опубликованной мной ссылке. Вы изменили на CustomObservableCollection<TextDocument> везде. Меня устраивает.

Kris
11 августа 2021 в 03:07
0

Я получил ошибку в this.Update_MyDocuments() в MainPage.g.cs. Если эту ошибку как-то убрать, все будет нормально работать.

mm8
11 августа 2021 в 07:12
0

Класс MainPage.g.cs создается на основе вашего кода. Какую ошибку вы получаете? Вы даже не опубликовали реализацию Update_MyDocuments().

Kris
12 августа 2021 в 05:15
0

Сейчас это работает. Большое спасибо. Проблема была с IDE. Когда я изменил TabItemsSource с ObservableCollection на CustomObservableCollection, среда разработки Visual Studio не изменила его соответствующим образом в Main.g.cs. Удаление строки привязки TabItemsSource, сборка приложения и повторное добавление TabItemsSource помогли.

avatar
Chris
9 августа 2021 в 06:51
1

Я думаю, ваш код в конструкторе может нарушать первоначальную привязку к ObservableCollection. Попробуйте этот код:

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

public MainPage()
{
    this.InitializeComponent();
    MyDocuments.Add(new TextDocument());
}

Помогает?

Kris
9 августа 2021 в 07:30
1

Я попробую это и вернусь к вам

Kris
9 августа 2021 в 09:13
0

К сожалению, это решение тоже не решает проблему :-(