DEV Community

Discussion on: Touch001 Solving Tray Icon and minimalize UI problem on Arch Linux with C# in Avalonia

Collapse
 
axeia profile image
Axeia • Edited

Thank you very much, I doubt I could have gotten this to work if I hadn't found this post. As you mentioned the documentation for this is basically non-existent.
There is however a little mistake in the code in the end (I think?) and a little bit of room for improvement. The little mistake is the window never gets subscribed to the MyMainWindow_PropertyChanged method, so the window never gets hidden from the taskbar.

The little improvement I made is making the icon path non-static, I'm quite new to all of this so there might be a better way to go about loading the icon still but this should works as long as you are working within a Namespace and your project has a /Assets/icon.png in place.

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using ReactiveUI;
using System;
using System.Reflection;

namespace PcPunditCrawlerGUI
{
    public partial class App : Application
    {
        private MainWindow? myMainWindow;
        private void MyMainWindow_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
        {
            if (sender is MainWindow && e.NewValue is WindowState windowState && windowState == WindowState.Minimized)
            {
                myMainWindow?.Hide();
            }
        }

        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                myMainWindow = new MainWindow
                {
                    DataContext = new MainWindowViewModel(),
                };
                desktop.MainWindow = myMainWindow;
                myMainWindow.PropertyChanged += MyMainWindow_PropertyChanged;

                RegisterTrayIcon();
            }

            base.OnFrameworkInitializationCompleted();
        }

        private void RegisterTrayIcon()
        {
            var bitmap = new Bitmap(AvaloniaLocator.Current?.GetService<IAssetLoader>()?.Open(
                new Uri($"avares://{Assembly.GetExecutingAssembly().GetName().Name}/Assets/icon.png"))
            );
            var trayIcon = new TrayIcon
            {
                IsVisible = true,
                ToolTipText = "TestToolTipText",
                Command = ReactiveCommand.Create(ShowApplication),
                Icon = new WindowIcon(bitmap)
            };

            var trayIcons = new TrayIcons
        {
            trayIcon
        };

            SetValue(TrayIcon.IconsProperty, trayIcons);
        }

        private void ShowApplication()
        {
            if (myMainWindow != null)
            {
                myMainWindow.WindowState = WindowState.Normal;
                myMainWindow.Show();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
stipecmv profile image
Stipec

That is nice that my article helped someone :) I am working on different thing so my coding hobby went a little bit aside. Yes Avalonia offers multiple ways of loading images, it depends which suits you :)
The problem you mentioned I havent noticed when I played with it :O but indeed it needs to be fixed :)

Collapse
 
axeia profile image
Axeia

I figured out how to do this the MVVM (sort of) way as well - mainly thanks to looking at the code of WalletWasabi (github).

My App.axaml:

<Application xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="using:PcPunditCrawlerGUI"
             x:Class="PcPunditCrawlerGUI.App">
    <!-- I added xmlns:local="using:PcPunditCrawlerGUI"  -->
    <Application.DataTemplates>
        <local:ViewLocator/>
    </Application.DataTemplates>

    <Design.DataContext>
        <Application/>
    </Design.DataContext>


    <Application.Styles>
        <FluentTheme Mode="Dark"/>
        <StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
    </Application.Styles>

    <TrayIcon.Icons>
        <TrayIcons>
            <TrayIcon Icon="/Assets/titlebar_icon.ico" Command="{Binding ShowOrHideCommand}" ToolTipText="PCPundit Crawler">
                <NativeMenu.Menu>
                    <NativeMenu>                        
                        <NativeMenuItem Header="{Binding ShowOrHide}" Command="{Binding ShowOrHideCommand} " />                     
                        <NativeMenuItemSeparator />
                        <NativeMenuItem Header="Exit" Command="{Binding Exit}" />
                    </NativeMenu>
                </NativeMenu.Menu>
            </TrayIcon>
        </TrayIcons>
    </TrayIcon.Icons>
</Application>
Enter fullscreen mode Exit fullscreen mode

And my App.axaml.cs

   public partial class App : Application, INotifyPropertyChanged
    {
        public new event PropertyChangedEventHandler? PropertyChanged;
        private MainWindow? _myMainWindow;
        private bool _isHidden = false;
        public string ShowOrHide{ get => _isHidden ? "Show" : "Hide"; }

        private void MyMainWindow_PropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
        {
            if (sender is MainWindow && e.NewValue is WindowState windowState)
            {
                if(windowState == WindowState.Minimized)
                { 
                    _isHidden = true;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ShowOrHide)));
                    _myMainWindow?.Hide();
                }
                else
                {
                    _isHidden = false;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ShowOrHide)));
                    _myMainWindow?.Show();
                }
            }
        }

        public override void Initialize()
        {
            AvaloniaXamlLoader.Load(this);
        }

        public override void OnFrameworkInitializationCompleted()
        {
            if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
            {
                _myMainWindow = new MainWindow{ DataContext = new MainWindowViewModel() };
                desktop.MainWindow = _myMainWindow;
                _myMainWindow.PropertyChanged += MyMainWindow_PropertyChanged;
                DataContext = this;
            }
            base.OnFrameworkInitializationCompleted();
        }

        public void ShowOrHideCommand()
        {
            if (_isHidden)
                Show();
            else
                Hide();
        }

        public void Show()
        {
            if (_myMainWindow != null)
            {
                _myMainWindow.WindowState = WindowState.Normal;
                _myMainWindow.Show();
            }
        }

        public void Hide()
        {
            if(_myMainWindow != null)
            {
                _myMainWindow.WindowState = WindowState.Minimized;
                _myMainWindow.Hide();
            }
        }

        public void Exit()
        {
            _myMainWindow?.Close();            
        }
    }
Enter fullscreen mode Exit fullscreen mode

Disclaimer: I'm posting this rather late at night right as I got it working, don't know if further propagating the MainWindow.PropertyChanged event is the way to go. It does however work.

Collapse
 
stipecmv profile image
Stipec

Nice work, I was also wondering how to do it in View,(but as I wrote above, currently I do not have time to dive deeply in this topic to understand it complety,) because TrayIcon is UI component :)