AvaloniaUI / Avalonia

Develop Desktop, Embedded, Mobile and WebAssembly apps with C# and XAML. The most popular .NET UI client technology

Home Page:https://avaloniaui.net

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ComboBox with Tag throws exception when Fluent Theme is changed

kfandrew910 opened this issue · comments

Describe the bug

When interacting with the ComboBox (that has a Tag), specifically when clicking it, an exception is thrown (System.InvalidOperationException, The control already has a visual parent), resulting in a crash in the application.

The ComboBox named ThemeComboBox contains a list of Fluent theme options. When a theme option is selected, the ThemeComboBox_SelectionChanged event handler is triggered. This handler retrieves the selected theme key from the ComboBoxItem's Tag property and passes it to the ChangePalette method. This method dynamically changes the application's Fluent theme based on the selected theme key, updating the UI to reflect the new theme.

For some reason, it works as intended in the Designer/Preview in VS Code, but doesn't work on the running app.

In Preview/Designer:
2024-05-23-11-09-37-ezgif com-optimize

In Running Program:
2024-05-23-11-14-29-ezgif com-optimize

To Reproduce

You need the FluentAvalonia and reproduce this in Windows Desktop.

Im using Win10 Home for this.

You just need to update the current FluentTheme in the App and interact with a ComboBox that has a Tag in it.

It will throw the exception:
Excepción producida: 'System.InvalidOperationException' en Avalonia.Base.dll
Excepción no controlada del tipo 'System.InvalidOperationException' en Avalonia.Base.dll
The control already has a visual parent.

Expected behavior

Changes theme and is able to interact with the ComboBox again, as it does in the Designer/Preview of the MainView.axaml

Avalonia version

11.0.2

OS

Windows

Additional context

Code if needed:

In MainView.axaml:

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:vm="clr-namespace:UnoGisDesktopAndrew.ViewModels"
			 xmlns:views="clr-namespace:UnoGisDesktopAndrew.Views"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="500"
             x:Class="UnoGisDesktopAndrew.Views.MainView"
             x:DataType="vm:MainViewModel">
  <Design.DataContext>
    <!-- This only sets the DataContext for the previewer in an IDE,
         to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
    <vm:MainViewModel />
  </Design.DataContext>

	<DockPanel >
		<Panel DockPanel.Dock="Left">
			<StackPanel Name="ButtonPanel" Background="{DynamicResource SystemAccentColor}">
				<Button Name="ControlDisplay" Content="ControlDisplay" HorizontalAlignment="Stretch" CommandParameter="ControlDisplayView"/>
				<Button Name="XY" Content="XY" HorizontalAlignment="Stretch" CommandParameter="XYView"/>
				<Button Name="Increment" Content="Increment" HorizontalAlignment="Stretch" CommandParameter="IncrementView"/>
				<Button Name="Locations" Content="Locations" HorizontalAlignment="Stretch" CommandParameter="LocationsView"/>
				<Button Name="Find" Content="Find" HorizontalAlignment="Stretch" CommandParameter="FindView"/>
				<Button Name="Attachments" Content="Attachments" HorizontalAlignment="Stretch" CommandParameter="AttachmentsView"/>
				<Button Name="HistoricalDisplay" Content="HistoricalDisplay" HorizontalAlignment="Stretch" CommandParameter="HistoricalDisplayView"/>
				<Button Name="HistoricalDisplayDialog" Content="HistoricalDisplayDialog" HorizontalAlignment="Stretch" CommandParameter="HistoricalDisplayDialog"/>	
			</StackPanel>
			<Panel DockPanel.Dock="Left" VerticalAlignment="Bottom">
				<ToggleButton Name="ActualThemeToggleButton">
					<PathIcon Name="ActualThemeIcon" Data="M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A9.965 9.965 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146 1.232-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927 9.961 9.961 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662Z"/>
				</ToggleButton>
				
				<!-- Here is the relevant component -->
				<ComboBox Name="ThemeComboBox" HorizontalAlignment="Right" SelectionChanged="ThemeComboBox_SelectionChanged"
					  Margin="10">
					<ComboBoxItem Content="Lavander" Tag="Lavander"/>
					<ComboBoxItem Content="Forest" Tag="Forest"/>
					<ComboBoxItem Content="NightTime" Tag="NightTime"/>
					<ComboBoxItem Content="ToxicGreen" Tag="ToxicGreen"/>
				</ComboBox>
			</Panel>
		</Panel>

		
			<Panel Name="ViewsPanel">
				<Panel.Styles>
					<Style Selector="UserControl">
						<Setter Property="BorderBrush" Value="Black"/>
					</Style>
				</Panel.Styles>
				<!--
				<views:ControlDisplayView Name="ControlDisplayView" Padding="10"/>
				<views:XYView Name="XYView" Height="450" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"/>
				<views:IncrementView Name="IncrementView" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"/>
				<views:LocationsView Name="LocationsView" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"/>
				<views:FindView Name="FindView" Height="150" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1"/>
				<views:AttachmentsView Name="AttachmentsView" Height="150" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1" Margin="0 150"/>
				<views:HistoricalDisplayView Name="HistoricalDisplayView" Height="200" Width="300" Padding="10" BorderBrush="{DynamicResource SystemAccentColor}" BorderThickness="1" Margin="0 80"/>
				-->
			</Panel>
		
	</DockPanel>
	
</UserControl>

In MainView.axaml.cs

using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.Transformation;
using Avalonia.Styling;
using Avalonia.Themes.Fluent;
using Avalonia.Utilities;
using Avalonia.VisualTree;
using ReactiveUI;
using System;
using System.Diagnostics;
using System.Windows.Input;

namespace UnoGisDesktopAndrew.Views;

public partial class MainView : UserControl
{
    public MainView()
    {
        InitializeComponent();
    }

    protected override void OnInitialized()
    {
        base.OnInitialized();
        HideAllPanels();

        ActualThemeToggleButton.IsCheckedChanged += ChangeTheme;

        ControlDisplay.Click += Click;
        XY.Click += Click;
        Increment.Click += Click;
        Locations.Click += Click;
        Find.Click += Click;
        Attachments.Click += Click;
        HistoricalDisplay.Click += Click;
        HistoricalDisplayDialog.Click += ClickDialog;
    }

    //Important function
    private void ThemeComboBox_SelectionChanged(object? sender, Avalonia.Controls.SelectionChangedEventArgs e)
    {
        if (sender is ComboBox comboBox && comboBox.SelectedItem is ComboBoxItem selectedItem)
        {
            string selectedThemeKey = selectedItem.Tag.ToString();
            ChangePalette(selectedThemeKey);
        }
    }

    //Important function
    private void ChangePalette(string themeKey)
    {
        var app = (App)Application.Current;
        app.ChangeFluentTheme(themeKey);
    }

    private void ChangeTheme(object? sender, RoutedEventArgs e)
    {
        if ((bool) ActualThemeToggleButton.IsChecked)
        {
            //(this.GetVisualRoot() as Window).RequestedThemeVariant = ThemeVariant.Light;
            Debug.WriteLine("Changed to Light");
            (Application.Current as App).ToggleThemeVariant();
            ActualThemeIcon.Data = Geometry.Parse("M11.996 18.532a1 1 0 0 1 .993.883l.007.117v1.456a1 1 0 0 1-1.993.116l-.007-.116v-1.456a1 1 0 0 1 1-1Zm6.037-1.932 1.03 1.03a1 1 0 0 1-1.415 1.413l-1.03-1.029a1 1 0 0 1 1.415-1.414Zm-10.66 0a1 1 0 0 1 0 1.414l-1.029 1.03a1 1 0 0 1-1.414-1.415l1.03-1.03a1 1 0 0 1 1.413 0ZM12.01 6.472a5.525 5.525 0 1 1 0 11.05 5.525 5.525 0 0 1 0-11.05Zm8.968 4.546a1 1 0 0 1 .117 1.993l-.117.007h-1.456a1 1 0 0 1-.116-1.993l.116-.007h1.456ZM4.479 10.99a1 1 0 0 1 .116 1.993l-.116.007H3.023a1 1 0 0 1-.117-1.993l.117-.007h1.456Zm1.77-6.116.095.083 1.03 1.03a1 1 0 0 1-1.32 1.497L5.958 7.4 4.93 6.371a1 1 0 0 1 1.32-1.497Zm12.813.083a1 1 0 0 1 .083 1.32l-.083.094-1.03 1.03a1 1 0 0 1-1.497-1.32l.084-.095 1.029-1.03a1 1 0 0 1 1.414 0ZM12 2.013a1 1 0 0 1 .993.883l.007.117v1.455a1 1 0 0 1-1.993.117L11 4.468V3.013a1 1 0 0 1 1-1Z");
        } else
        {
            //(this.GetVisualRoot() as Window).RequestedThemeVariant = ThemeVariant.Dark;
            Debug.WriteLine("Changed to Dark");
            (Application.Current as App).ToggleThemeVariant();
            ActualThemeIcon.Data = Geometry.Parse("M20.026 17.001c-2.762 4.784-8.879 6.423-13.663 3.661A9.965 9.965 0 0 1 3.13 17.68a.75.75 0 0 1 .365-1.132c3.767-1.348 5.785-2.91 6.956-5.146 1.232-2.353 1.551-4.93.689-8.463a.75.75 0 0 1 .769-.927 9.961 9.961 0 0 1 4.457 1.327c4.784 2.762 6.423 8.879 3.66 13.662Z");
        }
    }

    private async void ClickDialog(object? sender, RoutedEventArgs e)
    {
        var win = new Window();
        win.Width = 500;
        win.Height = 200;
        win.CanResize = false;
        win.WindowStartupLocation = WindowStartupLocation.CenterOwner;

        /*
        var content = new HistoricalDisplayView();
        content.Padding = Thickness.Parse("10");

        win.Content = content;
        */

        await win.ShowDialog((Window)this.GetVisualRoot());
    }

    private void Click(object? sender, RoutedEventArgs e)
    {
        var button = (Button)e.Source;
        ShowViewByName((string)button.CommandParameter);
    }

    private void HideAllPanels()
    {
        foreach (var child in ViewsPanel.Children)
        {
            child.IsVisible = false;
        }
    }

    public void ShowViewByName(string name)
    {
        if (string.IsNullOrEmpty(name))
            return;

        var view = this.FindControl<UserControl>(name);

        if (view == null) return;

        HideAllPanels();
        view.IsVisible = true;
    }

    
}

In App.axaml:

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="UnoGisDesktopAndrew.App"
             RequestedThemeVariant="Dark">
             <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->

    <Application.Styles>
		<FluentTheme/>
	</Application.Styles>

	<Application.Resources>
		<ResourceDictionary>
			<ResourceDictionary.MergedDictionaries>
				<ResourceInclude Source="avares://UnoGisDesktopAndrew/FluentPalettes.axaml"/>
			</ResourceDictionary.MergedDictionaries>
		</ResourceDictionary>
	</Application.Resources>
</Application>

In App.axaml.cs:

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Themes.Fluent;
using System;
using System.Diagnostics;
using UnoGisDesktopAndrew.ViewModels;
using UnoGisDesktopAndrew.Views;

namespace UnoGisDesktopAndrew;

public partial class App : Application
{
    public override void Initialize()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public override void OnFrameworkInitializationCompleted()
    {
        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
        {
            desktop.MainWindow = new MainWindow
            {
                DataContext = new MainViewModel()
            };
        }
        else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
        {
            singleViewPlatform.MainView = new MainView
            {
                DataContext = new MainViewModel()
            };
        }

        base.OnFrameworkInitializationCompleted();
    }

    public void ToggleThemeVariant()
    {
        if (this.RequestedThemeVariant == ThemeVariant.Light)
        {
            RequestedThemeVariant = ThemeVariant.Dark;
        }
        else
        {
            RequestedThemeVariant = ThemeVariant.Light;
        }
    }

    public void ChangeFluentTheme(string themeKey)
    {

        // Get the resource dictionary containing the themes
        var themeDictionary = Resources.MergedDictionaries[0] as ResourceDictionary;
        if (themeDictionary == null)
        {
            throw new InvalidOperationException("Theme resource dictionary not found.");
        }

        // Retrieve the selected theme from the resource dictionary
        var selectedTheme = themeDictionary[themeKey] as FluentTheme;
        if (selectedTheme == null)
        {
            throw new InvalidOperationException($"Theme with key {themeKey} not found.");
        }

        // Clear the current styles
        Styles.Clear();

        // Add the selected theme to the application's styles
        Styles.Add(selectedTheme);

        // Refresh the theme variant to apply changes
        RequestedThemeVariant = RequestedThemeVariant;
    }
}

In FluentPalletes.axaml:

<ResourceDictionary xmlns="https://github.com/avaloniaui"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

	<!-- Themes created at: https://theme.xaml.live/ -->
		
		<FluentTheme x:Key="Lavander">
			<FluentTheme.Palettes>
				<ColorPaletteResources x:Key="Light" Accent="#ff8961cc" AltHigh="White" AltLow="White" AltMedium="White" AltMediumHigh="White" AltMediumLow="White" BaseHigh="Black" BaseLow="#ffeeceff" BaseMedium="#ffa987bc" BaseMediumHigh="#ff7b5890" BaseMediumLow="#ff9270a6" ChromeAltLow="#ff7b5890" ChromeBlackHigh="Black" ChromeBlackLow="#ffeeceff" ChromeBlackMedium="#ff7b5890" ChromeBlackMediumLow="#ffa987bc" ChromeDisabledHigh="#ffeeceff" ChromeDisabledLow="#ffa987bc" ChromeGray="#ff9270a6" ChromeHigh="#ffeeceff" ChromeLow="#fffeeaff" ChromeMedium="#fffbe4ff" ChromeMediumLow="#fffeeaff" ChromeWhite="White" ListLow="#fffbe4ff" ListMedium="#ffeeceff" RegionColor="#fffef6ff" />
				<ColorPaletteResources x:Key="Dark" Accent="#ff8961cc" AltHigh="Black" AltLow="Black" AltMedium="Black" AltMediumHigh="Black" AltMediumLow="Black" BaseHigh="White" BaseLow="#ff64576b" BaseMedium="#ffb6aabc" BaseMediumHigh="#ffcbbfd0" BaseMediumLow="#ff8d8193" ChromeAltLow="#ffcbbfd0" ChromeBlackHigh="Black" ChromeBlackLow="#ffcbbfd0" ChromeBlackMedium="Black" ChromeBlackMediumLow="Black" ChromeDisabledHigh="#ff64576b" ChromeDisabledLow="#ffb6aabc" ChromeGray="#ffa295a8" ChromeHigh="#ffa295a8" ChromeLow="#ff332041" ChromeMedium="#ff3f2e4b" ChromeMediumLow="#ff584960" ChromeWhite="White" ListLow="#ff3f2e4b" ListMedium="#ff64576b" RegionColor="#ff262738" />
			</FluentTheme.Palettes>
		</FluentTheme>
	
		<FluentTheme x:Key="Forest">
				<FluentTheme.Palettes>
					<ColorPaletteResources x:Key="Light" Accent="#ff34854d" AltHigh="White" AltLow="White" AltMedium="White" AltMediumHigh="White" AltMediumLow="White" BaseHigh="Black" BaseLow="#ffc2db65" BaseMedium="#ff7d9728" BaseMediumHigh="#ff4f6a00" BaseMediumLow="#ff668114" ChromeAltLow="#ff4f6a00" ChromeBlackHigh="Black" ChromeBlackLow="#ffc2db65" ChromeBlackMedium="#ff4f6a00" ChromeBlackMediumLow="#ff7d9728" ChromeDisabledHigh="#ffc2db65" ChromeDisabledLow="#ff7d9728" ChromeGray="#ff668114" ChromeHigh="#ffc2db65" ChromeLow="#ffe6f3bb" ChromeMedium="#ffdfeeaa" ChromeMediumLow="#ffe6f3bb" ChromeWhite="White" ListLow="#ffdfeeaa" ListMedium="#ffc2db65" RegionColor="#fff7ffff" />
					<ColorPaletteResources x:Key="Dark" Accent="#ff34854d" AltHigh="Black" AltLow="Black" AltMedium="Black" AltMediumHigh="Black" AltMediumLow="Black" BaseHigh="White" BaseLow="#ff784834" BaseMedium="#ffc5a294" BaseMediumHigh="#ffd8b8ac" BaseMediumLow="#ff9e7564" ChromeAltLow="#ffd8b8ac" ChromeBlackHigh="Black" ChromeBlackLow="#ffd8b8ac" ChromeBlackMedium="Black" ChromeBlackMediumLow="Black" ChromeDisabledHigh="#ff784834" ChromeDisabledLow="#ffc5a294" ChromeGray="#ffb28b7c" ChromeHigh="#ffb28b7c" ChromeLow="#ff46150a" ChromeMedium="#ff532215" ChromeMediumLow="#ff6c3b2a" ChromeWhite="White" ListLow="#ff532215" ListMedium="#ff784834" RegionColor="#ff353819" />
				</FluentTheme.Palettes>
		</FluentTheme>
	
		<FluentTheme x:Key="NightTime">
				<FluentTheme.Palettes>
					<ColorPaletteResources x:Key="Light" Accent="#ffcc4d11" AltHigh="White" AltLow="White" AltMedium="White" AltMediumHigh="White" AltMediumLow="White" BaseHigh="Black" BaseLow="#ff7cbee0" BaseMedium="#ff3282a8" BaseMediumHigh="#ff005a83" BaseMediumLow="#ff196e96" ChromeAltLow="#ff005a83" ChromeBlackHigh="Black" ChromeBlackLow="#ff7cbee0" ChromeBlackMedium="#ff005a83" ChromeBlackMediumLow="#ff3282a8" ChromeDisabledHigh="#ff7cbee0" ChromeDisabledLow="#ff3282a8" ChromeGray="#ff196e96" ChromeHigh="#ff7cbee0" ChromeLow="#ffc1e9fe" ChromeMedium="#ffb3e0f8" ChromeMediumLow="#ffc1e9fe" ChromeWhite="White" ListLow="#ffb3e0f8" ListMedium="#ff7cbee0" RegionColor="#ffcfeaff" />
					<ColorPaletteResources x:Key="Dark" Accent="#ffcc4d11" AltHigh="Black" AltLow="Black" AltMedium="Black" AltMediumHigh="Black" AltMediumLow="Black" BaseHigh="White" BaseLow="#ff2f7bad" BaseMedium="#ff8dbfdf" BaseMediumHigh="#ffa5d0ec" BaseMediumLow="#ff5e9dc6" ChromeAltLow="#ffa5d0ec" ChromeBlackHigh="Black" ChromeBlackLow="#ffa5d0ec" ChromeBlackMedium="Black" ChromeBlackMediumLow="Black" ChromeDisabledHigh="#ff2f7bad" ChromeDisabledLow="#ff8dbfdf" ChromeGray="#ff76aed3" ChromeHigh="#ff76aed3" ChromeLow="#ff093b73" ChromeMedium="#ff134b82" ChromeMediumLow="#ff266b9f" ChromeWhite="White" ListLow="#ff134b82" ListMedium="#ff2f7bad" RegionColor="#ff0d2644" />
				</FluentTheme.Palettes>
		</FluentTheme>
	
		<FluentTheme x:Key="ToxicGreen">
				<FluentTheme.Palettes>
					<ColorPaletteResources x:Key="Light" Accent="#ff00cf32" AltHigh="White" AltLow="White" AltMedium="White" AltMediumHigh="White" AltMediumLow="White" BaseHigh="Black" BaseLow="#ffa1ffad" BaseMedium="#ff4cb364" BaseMediumHigh="#ff148134" BaseMediumLow="#ff309a4c" ChromeAltLow="#ff148134" ChromeBlackHigh="Black" ChromeBlackLow="#ffa1ffad" ChromeBlackMedium="#ff148134" ChromeBlackMediumLow="#ff4cb364" ChromeDisabledHigh="#ffa1ffad" ChromeDisabledLow="#ff4cb364" ChromeGray="#ff309a4c" ChromeHigh="#ffa1ffad" ChromeLow="#ffd3ffda" ChromeMedium="#ffc9ffd1" ChromeMediumLow="#ffd3ffda" ChromeWhite="White" ListLow="#ffc9ffd1" ListMedium="#ffa1ffad" RegionColor="#ffd7ffdf" />
					<ColorPaletteResources x:Key="Dark" Accent="#ff00cf32" AltHigh="Black" AltLow="Black" AltMedium="Black" AltMediumHigh="Black" AltMediumLow="Black" BaseHigh="White" BaseLow="#ff333333" BaseMedium="#ff9a9a9a" BaseMediumHigh="#ffb4b4b4" BaseMediumLow="#ff676767" ChromeAltLow="#ffb4b4b4" ChromeBlackHigh="Black" ChromeBlackLow="#ffb4b4b4" ChromeBlackMedium="Black" ChromeBlackMediumLow="Black" ChromeDisabledHigh="#ff333333" ChromeDisabledLow="#ff9a9a9a" ChromeGray="Gray" ChromeHigh="Gray" ChromeLow="#ff151515" ChromeMedium="#ff1d1d1d" ChromeMediumLow="#ff2c2c2c" ChromeWhite="White" ListLow="#ff1d1d1d" ListMedium="#ff333333" RegionColor="#ff38423a" />
				</FluentTheme.Palettes>
		</FluentTheme>
	
</ResourceDictionary>

UPDATE:

Its not really ComboBox with Tags, its ANY ComboBox (sometimes they open, sometimes they dont).

Theming doesn't work well with replacing whole themes, as they include control templates replacement as well, forcing whole app UI to be rebuilt. At some point it crashes, when same child control was re-attached to another template parent.

Replacing ComboBox ComboBoxItem with ItemsSource would help in your case. But in general, I wouldn't suggest replacing FluentTheme in runtime for production apps (might be fine for a demo).

In ControlCatalog we reopen whole main window, when we switch full theme.