c# - 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# 代码中创建了所有控件(包括UniformGrid
许多ToggleButton
s),并在需要时将它们填充到 UI 中。
这对系统来说实际上是一个巨大的开销,而且不方便调试。
您的问题是它不遵循 MVVM 的设计模式。在 MVVM 中,请不要直接在 C# 代码中创建控件,而是使用数据类(Model
)进行操作。
View
in MVVM(Model, View, View-Model)可以看成是DataTemplate
UWP中的一个。推荐的方法如下:
- 创建一个类
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; }
}
- 创建一个
DataTemplate
inMainPage.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>
- 用于
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,则需要实现INotifyPropertyChanged
Model 的接口。
以下是一些可能对您有所帮助的文件:
更新
如果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>
推荐阅读
- html - 没有正确居中的 div“容器”
- python - 在一天中的每个小时拆分时间范围
- python - 根据月销售额实现python group by
- python - 如何通过在任何 XML 深度中通过正则表达式识别属性来使用 BeautifulSoup 查找 bs4 XML 属性值?
- python - 为什么我在 python 中得到一个带有 spotipy 的 attributeError?
- java - 错误:无法序列化值:应为“字符串”或“java.time.temporal.TemporalAccessor”,但为“时间戳”。”在带有 Graphql 的 Spring Boot 中
- java - 最小堆删除方法永远运行
- react-native - 如何检查电子邮件格式是否有效?并且两个密码匹配?反应原生
- docker - 用于 Openshift/Kubernetes 集群中 Docker 容器的自动化 SELinux 策略生成器工具
- javascript - 使用 getBufferSubData 获取 gl_Position 值