WinUI3设置AppBarButton的Icon属性时偶现参数异常的解决方法
我在WinUI3界面开发中,通过切换AppBarButton的Icon属性来展示设备连接状态:创建了两个全局FontIcon实例disconnectedIcon和connectedIcon,连接成功时切换为connectedIcon,断开时切回disconnectedIcon。点击tester1ConnectionButton或tester2ConnectionButton会弹出ContentDialog让用户输入设备信息,确认连接后执行Icon切换操作。但偶发以下异常,主要出现在ContentDialog确认后的代码块中:
System.ArgumentException: 'Value does not fall within the expected range.'
相关代码
MainWindow.xaml
<Window x:Class="TesterWinUI3_240606.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:TesterWinUI3_240606" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid RowDefinitions="auto,*,auto,auto,auto,*,auto,auto" ColumnDefinitions="auto, auto, auto, *"> <!--Upper part--> <Grid Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="3"> <Border BorderBrush="{ThemeResource ControlStrongStrokeColorDefaultBrush}" BorderThickness="2.5" CornerRadius="3" Margin="5"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center"> <!--Title at the top--> <TextBlock Text="Connection" FontSize="16" FontWeight="Bold" Margin="5"/> <!--Connection settings--> <AppBarElementContainer VerticalAlignment="Center"> <StackPanel Orientation="Vertical"> <TextBlock Text="Handler" Margin="5"/> <AppBarButton x:Name="handlerConnectionButton" Label="Disconnected" Width="80" Click="handlerConnectionButton_Click"> <FontIcon Glyph=""/> </AppBarButton> </StackPanel> </AppBarElementContainer> <AppBarSeparator/> <AppBarElementContainer VerticalAlignment="Center"> <StackPanel Orientation="Vertical"> <TextBlock x:Name="tester1NameTextBlock" Text="TESTER 1" Margin="5"/> <AppBarButton x:Name="tester1ConnectionButton" Label="Disconnected" Width="80" Click="tester1ConnectionButton_Click"> <FontIcon Glyph=""/> <!--<FontIcon Glyph=""/>--> </AppBarButton> </StackPanel> </AppBarElementContainer> <AppBarElementContainer VerticalAlignment="Center"> <StackPanel Orientation="Vertical"> <TextBlock x:Name="tester2NameTextBlock" Text="TESTER 2" Margin="5"/> <AppBarButton x:Name="tester2ConnectionButton" Label="Disconnected" Width="80" Click="tester2ConnectionButton_Click"> <FontIcon Glyph=""/> </AppBarButton> </StackPanel> </AppBarElementContainer> </StackPanel> </Border> </Grid> <!-- Other UI --> </Grid> </Window>
MainWindow.xaml.cs
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Data; using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Navigation; using System.Drawing.Text; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.ApplicationModel.DataTransfer; namespace TesterWinUI3_240606 { public sealed partial class MainWindow : Window { // Flags for connection status private bool isHandlerConnected = false; private bool isTester1Connected = false; private bool isTester2Connected = false; FontIcon disconnectedIcon; FontIcon connectedIcon; private string tester1Name = "TESTER 1"; private string tester2Name = "TESTER 2"; public MainWindow() { this.InitializeComponent(); // Icon disconnectedIcon = new FontIcon(); disconnectedIcon.Glyph = "\uF78A"; // -> CancelMedium, unicode character code disconnectedIcon.FontFamily = new FontFamily("Segoe MDL2 Assets"); connectedIcon = new FontIcon(); connectedIcon.Glyph = "\uF78C"; // -> AcceptMedium, unicode character code connectedIcon.FontFamily = new FontFamily("Segoe MDL2 Assets"); } private void handlerConnectionButton_Click(object sender, RoutedEventArgs e) { if (isHandlerConnected == true) { // Disconnect from handler isHandlerConnected = false; handlerConnectionButton.Label = "Disconnected"; handlerConnectionButton.Icon = disconnectedIcon; return; } // Connect to handler isHandlerConnected = true; handlerConnectionButton.Label = "Connected"; handlerConnectionButton.Icon = connectedIcon; } private async void tester1ConnectionButton_Click(object sender, RoutedEventArgs e) { if (isTester1Connected == true) { // Disconnect from TESTER 1 isTester1Connected = false; tester1ConnectionButton.Label = "Disconnected"; tester1ConnectionButton.Icon = disconnectedIcon; return; } // Connect to TESTER 1 ContentDialog connectDialog = new ContentDialog { XamlRoot = Content.XamlRoot, Title = "Connect to TESTER 1", Content = new StackPanel { Orientation = Orientation.Vertical, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Children = { new TextBlock { Text = "Name the TESTER:" }, new TextBox { Name = "tester1NameTextBox", PlaceholderText = tester1Name }, new TextBlock { Text = "Enter the IP address of TESTER:" }, new TextBox { Name = "tester1IP"} }, Padding = new Thickness(5), }, PrimaryButtonText = "Connect", CloseButtonText = "Cancel", DefaultButton = ContentDialogButton.Primary, }; ContentDialogResult result = await connectDialog.ShowAsync(); if (result == ContentDialogResult.Primary) { TextBox nameTextBox = (TextBox)((StackPanel)connectDialog.Content).Children[1]; TextBox ipTextBox = (TextBox)((StackPanel)connectDialog.Content).Children[3]; tester1Name = nameTextBox.Text.Trim(); string ip = ipTextBox.Text.Trim(); if (string.IsNullOrEmpty(ip) == false) { // Connect to TESTER 1 isTester1Connected = true; tester1ConnectionButton.Label = "Connected"; tester1ConnectionButton.Icon = connectedIcon; if (string.IsNullOrEmpty(tester1Name) == false) { tester1NameTextBlock.Text = tester1Name; } } } } private async void tester2ConnectionButton_Click(object sender, RoutedEventArgs e) { if (isTester2Connected == true) { // Disconnect from TESTER 2 isTester2Connected = false; tester2ConnectionButton.Label = "Disconnected"; tester2ConnectionButton.Icon = disconnectedIcon; return; } // Connect to TESTER 2 ContentDialog connectDialog = new ContentDialog { XamlRoot = Content.XamlRoot, Title = "Connect to TESTER 2", Content = new StackPanel { Orientation = Orientation.Vertical, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Children = { new TextBlock { Text = "Name the TESTER:" }, new TextBox { Name = "tester2NameTextBox", PlaceholderText = tester2Name }, new TextBlock { Text = "Enter the IP address of TESTER:" }, new TextBox { Name = "tester2IP"} }, Padding = new Thickness(5), }, PrimaryButtonText = "Connect", CloseButtonText = "Cancel", DefaultButton = ContentDialogButton.Primary, }; ContentDialogResult result = await connectDialog.ShowAsync(); if (result == ContentDialogResult.Primary) { TextBox nameTextBox = (TextBox)((StackPanel)connectDialog.Content).Children[1]; TextBox ipTextBox = (TextBox)((StackPanel)connectDialog.Content).Children[3]; tester2Name = nameTextBox.Text.Trim(); string ip = ipTextBox.Text.Trim(); if (string.IsNullOrEmpty(ip) == false) { // Connect to TESTER 2 isTester2Connected = true; tester2ConnectionButton.Label = "Connected"; tester2ConnectionButton.Icon = connectedIcon; if (string.IsNullOrEmpty(tester2Name) == false) { tester2NameTextBlock.Text = tester2Name; } } } } } }
问题原因与解决方案
原因
WinUI3中,单个UI元素(如FontIcon实例)不能同时被多个父控件引用。当你把同一个connectedIcon或disconnectedIcon赋值给多个AppBarButton.Icon时,控件树会出现冲突,导致偶发的ArgumentException——尤其是快速操作多个按钮切换状态时,同一个FontIcon实例可能被尝试附加到多个父节点,触发异常。
解决方法
方法1:为每个按钮维护独立的状态Icon
不再复用全局FontIcon实例,而是为每个按钮分别创建专属的connected和disconnected图标:
// 替换原有全局FontIcon声明,改为每个按钮单独的实例 private FontIcon handlerDisconnectedIcon, handlerConnectedIcon; private FontIcon tester1DisconnectedIcon, tester1ConnectedIcon; private FontIcon tester2DisconnectedIcon, tester2ConnectedIcon; public MainWindow() { this.InitializeComponent(); // 初始化每个按钮的Icon实例 handlerDisconnectedIcon = CreateFontIcon("\uF78A"); handlerConnectedIcon = CreateFontIcon("\uF78C"); tester1DisconnectedIcon = CreateFontIcon("\uF78A"); tester1ConnectedIcon = CreateFontIcon("\uF78C"); tester2DisconnectedIcon = CreateFontIcon("\uF78A"); tester2ConnectedIcon = CreateFontIcon("\uF78C"); } // 封装创建FontIcon的辅助方法,减少重复代码 private FontIcon CreateFontIcon(string glyph) { return new FontIcon { Glyph = glyph, FontFamily = new FontFamily("Segoe MDL2 Assets") }; }
然后在按钮点击事件中使用对应按钮的专属Icon:
// handler按钮切换逻辑 handlerConnectionButton.Icon = isHandlerConnected ? handlerConnectedIcon : handlerDisconnectedIcon; // tester1按钮切换逻辑 tester1ConnectionButton.Icon = isTester1Connected ? tester1ConnectedIcon : tester1DisconnectedIcon; // tester2按钮切换逻辑 tester2ConnectionButton.Icon = isTester2Connected ? tester2ConnectedIcon : tester2DisconnectedIcon;
方法2:切换时动态生成新的FontIcon
如果不想维护多个实例,也可以在每次切换状态时创建新的FontIcon对象:
// 断开连接时 tester1ConnectionButton.Icon = new FontIcon { Glyph = "\uF78A", FontFamily = new FontFamily("Segoe MDL2 Assets") }; // 连接成功时 tester1ConnectionButton.Icon = new FontIcon { Glyph = "\uF78C", FontFamily = new FontFamily("Segoe MDL2 Assets") };
额外修正
原代码中tester1ConnectionButton_Click方法里有拼写错误:if (string.IsNullOrEmpty(stest1Name) == false),应改为tester1Name,避免逻辑异常。
内容的提问来源于stack exchange,提问作者FUSE0604

