首页 > 解决方案 > XAML UI 使用 Observable 集合 MVVM 很慢

问题描述

这是我的第一个问题。我已阅读与我的问题相关的主题,但没有找到解决方案。

在我的 MainViewModel 中有 10 个对象持有绑定到可观察集合的按钮数组。我想在对象之间切换以显示当前对象。这种方式更新 ui 很慢。我不确定我可以尝试什么。

编辑:我试图创建一个
最小的、可重现的示例:项目名称:STACK_MVVM

主页.xaml

<Page
x:Class="STACK_MVVM.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:STACK_MVVM"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

<Grid x:Name="thegrid" Margin="0,0,0,0">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"/>
        <ColumnDefinition Width="6*"/>
        <ColumnDefinition Width="2*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="0.5*"/>
        <RowDefinition Height="5*"/>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="2*"/>
    </Grid.RowDefinitions>
    <controls:UniformGrid x:Name="ButtonsUniformGrid"  Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="1"  Orientation="Horizontal"  Columns="16" Rows="5"  ColumnSpacing="4" RowSpacing="4">
    </controls:UniformGrid>
    <controls:UniformGrid x:Name="ButtonsUniformGrid_Copy"  Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="1"  Orientation="Horizontal"  Columns="10" Rows="3"  ColumnSpacing="4" RowSpacing="4" Margin="0,15,0,0">

    </controls:UniformGrid>
</Grid>
</Page>

MainPage.xaml.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;



namespace STACK_MVVM
{

public sealed partial class MainPage : Page
{
    public Room[] room = new Room[10];
    public ToggleButton[] channelSel = new ToggleButton[10];
    Binding[] myChanSel_Binding_Command = new Binding[10];
    public MainViewModel TheMainViewModel1 { get; set; }
    int tabentry = 0;
    public MyLogic TheLogic = null;
    public MainPage()
    {
        this.InitializeComponent();
        this.TheMainViewModel1 = new MainViewModel();
        this.TheMainViewModel1.fillItems();
         TheLogic = new MyLogic();
         TheLogic.setTheMainModel(TheMainViewModel1);


        ButtonsUniformGrid.Visibility = Visibility.Visible;
        ButtonsUniformGrid_Copy.Orientation = Orientation.Horizontal;
        ButtonsUniformGrid_Copy.Columns = 16;
        ButtonsUniformGrid_Copy.Rows = 4;
        for (int i = 0; i < 10; i++)
        {
            room[i] = new Room();
            room[i].channel = i;
            TheLogic.setTheModels(room[i].TheMainViewModel1, i);
            thegrid.Children.Add(room[i].uniformGrid1);

            Grid.SetColumn(room[i].uniformGrid1, 1);     //ToggleButtonMatrix
            Grid.SetRow(room[i].uniformGrid1, 1);

            //****
            channelSel[i] = new ToggleButton();
            channelSel[i].HorizontalAlignment = HorizontalAlignment.Stretch;
            channelSel[i].VerticalAlignment = VerticalAlignment.Stretch;
            channelSel[i].Checked += HandleChannelSelChecked;
            channelSel[i].Tag = i;

            channelSel[i].SetBinding(ToggleButton.CommandProperty, new Binding() { Source = TheMainViewModel1, Path = new PropertyPath("OKButtonClicked1") });
            channelSel[i].SetBinding(ToggleButton.CommandParameterProperty, new Binding() { Source = TheMainViewModel1, Path = new PropertyPath("MyChannel[" + i + "]") });

            ButtonsUniformGrid_Copy.Children.Add(channelSel[i]);
        }

    }
    private void HandleChannelSelChecked(object sender, RoutedEventArgs e)   // make it Visible
    {
        ToggleButton toggle = sender as ToggleButton;
        int m = (int)toggle.Tag;
        for (int i = 0; i < 10; i++)
        {
            if (i != m)
            {
                room[i].uniformGrid1.Visibility = Visibility.Collapsed;
                channelSel[i].IsChecked = false;
            }
        }
        room[m].uniformGrid1.Visibility = Visibility.Visible;
    }
}
}

房间.cs

using Microsoft.Toolkit.Uwp.UI.Controls;
using System;
using System.Collections.Generic;
using System.Linq;

using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;

namespace STACK_MVVM
{
    public class Room
    {
        public UniformGrid uniformGrid1 = new UniformGrid();
        public Dictionary<ToggleButton, Tuple<int, int>> clientDict = new Dictionary<ToggleButton, Tuple<int, int>>();
        public ToggleButton[,] bu = new ToggleButton[5, 16];
        public int channel;

        public struct pattern
        {
            public int[,] vec_bs1;
            public int[] vec_bs;
        };
        public pattern thepattern = new pattern();
        public MainViewModel TheMainViewModel1 { get; set; }
        Binding[] Toggle_Binding = new Windows.UI.Xaml.Data.Binding[5 * 16];

    public Room()
    {
        TheMainViewModel1 = new MainViewModel();
        TheMainViewModel1.fillItems();
        thepattern.vec_bs1 = new int[5, 16];
        thepattern.vec_bs = new int[80 * 10];

        uniformGrid1.Columns = 16;
        uniformGrid1.Rows = 5;
        uniformGrid1.ColumnSpacing = 4;
        uniformGrid1.RowSpacing = 4;
        uniformGrid1.Orientation = Orientation.Horizontal;
        uniformGrid1.Visibility = Visibility.Collapsed;

        for (int i = 0; i < 5; i++)
        {
            for (int j = 0; j < 16; j++)
            {
                bu[i, j] = new ToggleButton();

                clientDict.Add(bu[i, j], new Tuple<int, int>(i, j));
                bu[i, j].HorizontalAlignment = HorizontalAlignment.Stretch;
                bu[i, j].VerticalAlignment = VerticalAlignment.Stretch;                
                uniformGrid1.Children.Add(bu[i, j]);
                //BINDINGS
                Toggle_Binding[(j) + (i * 16)] = new Windows.UI.Xaml.Data.Binding();
                Toggle_Binding[(j) + (i * 16)].Source = this.TheMainViewModel1;
                string ppath = "MyItemsbool[" + ((j) + (i * 16)) + "]";
                Toggle_Binding[(j) + (i * 16)].Path = new PropertyPath(ppath);
                Toggle_Binding[(j) + (i * 16)].Mode = BindingMode.TwoWay;
                Toggle_Binding[(j) + (i * 16)].UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                BindingOperations.SetBinding(bu[i, j], ToggleButton.IsCheckedProperty, Toggle_Binding[(j) + (i * 16)]);

            }
        }
    }
    private void HandleToggleButtonUnChecked(object sender, RoutedEventArgs e)
    {
       ToggleButton toggle = sender as ToggleButton;
       var client = clientDict[sender as ToggleButton];
       // Debug.WriteLine(client.Item1 + " " + client.Item2);
        thepattern.vec_bs1[client.Item1, client.Item2] = 0;
    }
    public void HandleToggleButtonChecked(object sender, RoutedEventArgs e)
    {
        ToggleButton toggle = sender as ToggleButton;
        var client = clientDict[sender as ToggleButton];
       // Debug.WriteLine(client.Item1 + " " + client.Item2);
        this.thepattern.vec_bs1[client.Item1, client.Item2] = 1;
    }
}//Class
}

MyLogic.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace STACK_MVVM
{   public class MyLogic
   {
        public static MainViewModel[] TheModels = new MainViewModel[10];
        public MainViewModel TheMainModel = new MainViewModel();

        public void setTheModels(MainViewModel themodel, int num)
        {
            TheModels[num] = themodel;
            //Debug.WriteLine("THEMODELS" + TheModels[0].MyItemsbool[0]);
        }
        public void setTheMainModel(MainViewModel themainmodel)
        {
            TheMainModel = themainmodel;
        }
        public static void LoadPattern(object parameter)
        {
           for (int x = 0; x < 10; x++)
           {
                TheModels[x].pattern_load_struct((int)parameter);
           }
            Debug.Write("CHANNELNUM: " + parameter);
        }
   }
}

MainViewModelBase.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel.Core;
using Windows.UI.Core;

namespace STACK_MVVM
{
    public abstract class MainViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private CoreDispatcher _dispatcher = CoreApplication.MainView.Dispatcher;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            if (_dispatcher.HasThreadAccess)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            else
            {
                _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                });
            }
        }
    }
}

主视图模型.cs

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace STACK_MVVM
{
    public class MainViewModel : MainViewModelBase
    {
        public struct pattern
        {   
            public int[] vec_bs1;
            public int[] vec_bs;
        };
        public pattern thepattern = new pattern();

        private ObservableCollection<int> _myChannel = new ObservableCollection<int>(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 });
        private ObservableCollection<bool> _myItemsbool = new ObservableCollection<bool>(new[] { true, false, true });    
        public MainViewModel()
        {
            thepattern.vec_bs1 = new int[5 * 16];
            thepattern.vec_bs = new int[80 * 10];
        }
        public ObservableCollection<bool> MyItemsbool
        {
            get { return _myItemsbool; }
            set
            {
                _myItemsbool = value;
            }
        }
        public ObservableCollection<int> MyChannel { get => _myChannel; set => _myChannel = value; }
        public void fillItems()
        {

            for (int i = 0; i < 5; i++)
                for (int j = 0; j < 16; j++)
                {
                    {

                        MyItemsbool.Add(true);
                        MyItemsbool[(j) + (i * 16)] = true;
                        //MyItemsbool[(j) + (i * 16)] = rnd.Next(2) !=0;
                    }
                }
        }
        public ICommand OKButtonClicked1
        {
            get
            {
               return new DelegateCommand1<object>(MyLogic.LoadPattern);
            }
        }

       public void pattern_save_struct(int tabentry)
       {
            for (int i = 0; i < 5; i++)
            {
                for (int j = 0; j < 16; j++)
                {
                    thepattern.vec_bs[(j) + (i * 16) + ((80) * tabentry)] = (MyItemsbool[(j) + (i * 16)]) ? 1 : 0;
                    //  thepattern.vec_bs[(j) + (i * 16) + ((80) * tabentry)] = thepattern.vec_bs1[i, j];   
                    // thepattern.vec_bs[(j) + (i * 16) + ((80) * tabentry)] = thepattern.vec_bs1[i, j];   
                }
            }
       }
        public async Task pattern_load_struct(int tabentry)
        {
            var rnd = new Random();  //this is just for testing - Randomly Activate Cell

                await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                Array.Copy(thepattern.vec_bs, 80 * tabentry, thepattern.vec_bs1, 0, 80);
                // Array.Copy(thepattern.vec_bs1, 0, MyItemsbool, 0, 80); //DONT WORK

                for (int i = 0; i < 80; i++)
                {
                     thepattern.vec_bs1[i] = rnd.Next(2);            // Randomly activate Cell - this line can be deletet.

                    MyItemsbool[i] = thepattern.vec_bs1[i] != 0;
                }
            });
        }
    }
}

委托命令1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace STACK_MVVM
{
    class DelegateCommand1<T> : ICommand
    {
        private readonly Action<T> handler;
        private bool isEnabled = true;
        public event EventHandler CanExecuteChanged;
        public delegate void SimpleEventHandler();
        public DelegateCommand1(Action<T> handler)
        {
            this.handler = handler;
        }
        public bool IsEnabled
        {
            get
            {
                return this.isEnabled;
            }
        }
        void ICommand.Execute(object org)
        {
            this.handler((T)org);
        }
        bool ICommand.CanExecute(object org)
        {
            return this.IsEnabled;
        }
        private void OnCanExecuteChanged()
        {
            if (this.CanExecuteChanged != null)
            {
                this.CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }
}

标签: c#xamlmvvmuwp

解决方案


我查看了您的代码,您似乎已经在 C# 代码中创建了所有控件(包括UniformGrid许多ToggleButtons),并在需要时将它们填充到 UI 中。

这对系统来说实际上是一个巨大的开销,而且不方便调试。

您的问题是它不遵循 MVVM 的设计模式。在 MVVM 中,请不要直接在 C# 代码中创建控件,而是使用数据类(Model)进行操作。

Viewin MVVM(Model, View, View-Model)可以看成是DataTemplateUWP中的一个。推荐的方法如下:

  1. 创建一个类room和一个类apartment
public class Room
{
    public bool IsOpened { get; set; }
    public string Name { get; set; }
}
public class Apartment
{
    public string Name { get; set; }
    public List<Room> Rooms { get; set; }
}
  1. 创建一个DataTemplateinMainPage.xaml并使用绑定来绑定相应的属性。
<Page.Resources>
    <DataTemplate x:DataType="local:Room" x:Key="RoomItemTemplate">
        <ToggleButton IsChecked="{x:Bind IsOpened}"/>
    </DataTemplate>
    <DataTemplate x:DataType="local:Apartment" x:Key="ApartmentItemTemplate">
        <ToggleButton Content="{x:Bind Name}"/>
    </DataTemplate>
</Page.Resources>
  1. 用于GridView绑定集合。

xml

...
<GridView ItemTemplate="{StaticResource RoomItemTemplate}"
          x:Name="RoomGridView"
          />

<GridView ItemTemplate="{StaticResource ApartmentItemTemplate}"
          x:Name="ApartmentGridView"
          IsItemClickEnabled="True"
          ItemsSource="{x:Bind ApartmentCollection}"
          ItemClick="ApartmentGridView_ItemClick"/>
...

xml.cs

public ObservableCollection<Apartment> ApartmentCollection = new ObservableCollection<Apartment>();
//...
private void ApartmentGridView_ItemClick(object sender, ItemClickEventArgs e)
{
    var item = e.ClickedItem as Apartment;
    RoomGridView.ItemsSource = item.Rooms;
}

需要注意的是,以上代码为简化代码。如果要将当前代码迁移到此模式,则需要重新组织代码。

使用 GridView,您可以使用控件本身的虚拟化来减少资源消耗。同时,这种DataTemplate方法可以大大简化代码编写。

关于你说的改变单个项目的状态,ObservableCollection没有回应。这是正常的,因为ObservableCollection只响应集合中项目数量的变化。如果要在更改数据类的属性时通知 UI,则需要实现INotifyPropertyChangedModel 的接口。


以下是一些可能对您有所帮助的文件:


更新

如果GridView仍然卡住,这可能是其默认动画计算造成的。你可以用它ItemsControl来解决这个问题。

<ItemsControl ItemTemplate="{StaticResource RoomItemTemplate}"
  x:Name="RoomGridView"
          Grid.Column="0" Grid.Row="0">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <controls:UniformGrid Columns="16" Rows="5" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

推荐阅读