codememo

를 사용하여 Windows에서 팝업 "토스터" 알림을 만듭니다.그물

tipmemo 2023. 5. 13. 10:14
반응형

를 사용하여 Windows에서 팝업 "토스터" 알림을 만듭니다.그물

사용하고 있습니다.NET 및 특정 이벤트가 트리거될 때 내 데스크톱 구석에 알림을 표시하는 데스크톱 앱/서비스를 만들고 있습니다.너무 거슬리는 일반 메시지 상자 b/c는 사용하고 싶지 않습니다.알림을 보기로 이동한 다음 몇 초 후에 사라집니다.저는 새로운 메시지가 도착했을 때 받는 Outlook 알림과 매우 유사한 기능을 할 수 있는 것을 생각하고 있습니다.문제는 이것을 위해 WPF를 사용해야 하는가 하는 것입니다.저는 WPF로 아무것도 해본 적이 없지만 그것이 끝까지 최선의 수단이라면 기꺼이 시도할 것입니다.이것을 정기적으로 수행할 수 있는 방법이 있습니까?NET 라이브러리?

WPF는 이것을 절대적으로 사소한 것으로 만듭니다.아마 10분 정도 걸릴 겁니다.다음은 단계입니다.

  1. 창을 만들고 AllowsTransparency="true"를 설정한 후 창에 그리드를 추가합니다.
  2. 그리드의 렌더 변환을 원점이 0.1인 축척 변환으로 설정
  3. 그리드에 ScaleX 0에서 1로 애니메이션을 만든 다음 나중에 불투명도를 1에서 0으로 애니메이션을 만듭니다.
  4. 생성자에서 Window를 계산합니다.상단 및 창.왼쪽 - 화면의 오른쪽 아래 모서리에 창을 배치합니다.

그게 다야.

Expression Blend를 사용하여 다음 작업 코드를 생성하는 데 약 8분이 걸렸습니다.

<Window
    x:Class="NotificationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Notification Popup" Width="300" SizeToContent="Height"
  WindowStyle="None" AllowsTransparency="True" Background="Transparent">

  <Grid RenderTransformOrigin="0,1" >

    <!-- Notification area -->
    <Border BorderThickness="1" Background="Beige" BorderBrush="Black" CornerRadius="10">
      <StackPanel Margin="20">
        <TextBlock TextWrapping="Wrap" Margin="5">
          <Bold>Notification data</Bold><LineBreak /><LineBreak />
          Something just happened and you are being notified of it.
        </TextBlock>
        <CheckBox Content="Checkable" Margin="5 5 0 5" />
        <Button Content="Clickable" HorizontalAlignment="Center" />
      </StackPanel>
    </Border>

    <!-- Animation -->
    <Grid.Triggers>
      <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard>
          <Storyboard>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)">
              <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
              <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
            </DoubleAnimationUsingKeyFrames>
            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)">
              <SplineDoubleKeyFrame KeyTime="0:0:2" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="0:0:4" Value="0"/>
            </DoubleAnimationUsingKeyFrames>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Grid.Triggers>

    <Grid.RenderTransform>
      <ScaleTransform ScaleY="1" />
    </Grid.RenderTransform>

  </Grid>

</Window>

코드 뒤에 있는 경우:

using System;
using System.Windows;
using System.Windows.Threading;

public partial class NotificationWindow
{
  public NotificationWindow()
  {
    InitializeComponent();

    Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
    {
      var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
      var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
      var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));

      this.Left = corner.X - this.ActualWidth - 100;
      this.Top = corner.Y - this.ActualHeight;
    }));
  }
}

WPF가 정규 중 하나이기 때문입니다.NET 라이브러리의 답은 "일반"으로 이를 달성할 수 있다는 것입니다.NET 라이브러리".

WPF를 사용하지 않고 이 작업을 수행할 수 있는 방법이 있는지 묻는 질문에는 여전히 "예"라고 답하지만, 매우 복잡하며 5분 이상이 소요됩니다.

저는 이를 위해 "토스트 팝업"과 "도움 풍선"을 제어하는 CodePlex 사이트를 만들었습니다.이러한 버전에는 아래에 설명된 것보다 더 많은 기능이 있습니다.https://toastspopuphelpballoon.codeplex.com .

이것은 제가 찾고 있던 솔루션을 위한 훌륭한 출발점이었습니다.요구 사항을 충족하기 위해 몇 가지 수정 작업을 수행했습니다.

  • 저는 마우스 오버로 애니메이션을 멈추고 싶었습니다.
  • 마우스가 나갈 때 "재설정" 애니메이션.
  • 불투명도가 0에 도달하면 창을 닫습니다.
  • 토스트를 쌓습니다(창 수가 화면 높이를 초과하는 경우 문제를 해결하지 않았습니다).
  • 내 뷰 모델에서 호출 로드

여기 제 XAML이 있습니다.

<Window x:Class="Foundation.FundRaising.DataRequest.Windows.NotificationWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="NotificationWindow" Height="70" Width="300" ShowInTaskbar="False"
    WindowStyle="None" AllowsTransparency="True" 
    Background="Transparent">

<Grid RenderTransformOrigin="0,1" >
    <Border BorderThickness="2" Background="{StaticResource GradientBackground}" BorderBrush="DarkGray" CornerRadius="7">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="60"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="24"/>
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="30"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>

            <Image Grid.Column="0" 
                   Grid.RowSpan="2" 
                   Source="Resources/data_information.png" 
                   Width="40" Height="40" 
                   VerticalAlignment="Center" 
                   HorizontalAlignment="Center"/>

            <Image Grid.Column="2" 
                   Source="Resources/error20.png"
                   Width="20" 
                   Height="20" 
                   VerticalAlignment="Center" 
                   ToolTip="Close"
                   HorizontalAlignment="Center" 
                   Cursor="Hand" MouseUp="ImageMouseUp"/>

            <TextBlock Grid.Column="1" 
                       Grid.Row="0"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center"
                       FontWeight="Bold" FontSize="15"
                       Text="A Request has been Added"/>

            <Button Grid.Column="1"
                    Grid.Row="1"
                    FontSize="15"
                    Margin="0,-3,0,0"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Content="Click Here to View" 
                    Style="{StaticResource LinkButton}"/>
        </Grid>            
    </Border>

    <!-- Animation -->
    <Grid.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard x:Name="StoryboardLoad">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="0.0" To="1.0" Duration="0:0:2" />
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:5" Completed="DoubleAnimationCompleted"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.MouseEnter">
            <EventTrigger.Actions>
                <RemoveStoryboard BeginStoryboardName="StoryboardLoad"/>
                <RemoveStoryboard BeginStoryboardName="StoryboardFade"/>
            </EventTrigger.Actions>
        </EventTrigger>

        <EventTrigger RoutedEvent="Mouse.MouseLeave">
            <BeginStoryboard x:Name="StoryboardFade">
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetProperty="(UIElement.Opacity)" From="1.0" To="0.0" Duration="0:0:8" BeginTime="0:0:2" Completed="DoubleAnimationCompleted"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>

    </Grid.Triggers>

    <Grid.RenderTransform>
        <ScaleTransform ScaleY="1" />
    </Grid.RenderTransform>
</Grid>

코드비하인드

public partial class NotificationWindow : Window
{
    public NotificationWindow()
        : base()
    {
        this.InitializeComponent();
        this.Closed += this.NotificationWindowClosed;
    }

    public new void Show()
    {
        this.Topmost = true;
        base.Show();

        this.Owner = System.Windows.Application.Current.MainWindow;
        this.Closed += this.NotificationWindowClosed;
        var workingArea = Screen.PrimaryScreen.WorkingArea;

        this.Left = workingArea.Right - this.ActualWidth;
        double top = workingArea.Bottom - this.ActualHeight;

        foreach (Window window in System.Windows.Application.Current.Windows)
        {                
            string windowName = window.GetType().Name;

            if (windowName.Equals("NotificationWindow") && window != this)
            {
                window.Topmost = true;
                top = window.Top - window.ActualHeight;
            }
        }

        this.Top = top;
    }
    private void ImageMouseUp(object sender, 
        System.Windows.Input.MouseButtonEventArgs e)
    {
        this.Close();
    }

    private void DoubleAnimationCompleted(object sender, EventArgs e)
    {
        if (!this.IsMouseOver)
        {
            this.Close();
        }
    }
}

View 모델의 호출:

    private void ShowNotificationExecute()
    {
        App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
            () =>
            {
                var notify = new NotificationWindow();
                notify.Show();
            }));
    }

XAML에서 참조된 스타일:

     <Style x:Key="LinkButton" TargetType="Button">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <TextBlock>
                        <ContentPresenter />
                    </TextBlock>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Foreground" Value="Blue"/>
        <Setter Property="Cursor" Value="Hand"/>
        <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
                <Setter Property="ContentTemplate">
                    <Setter.Value>
                        <DataTemplate>
                            <TextBlock TextDecorations="Underline" Text="{TemplateBinding Content}"/>
                        </DataTemplate>
                    </Setter.Value>
                </Setter>
            </Trigger>
        </Style.Triggers>
    </Style>

    <LinearGradientBrush x:Key="GradientBackground" EndPoint="0.504,1.5" StartPoint="0.504,0.03">
        <GradientStop Color="#FFFDD5A7" Offset="0"/>
        <GradientStop Color="#FFFCE79F" Offset="0.567"/>
    </LinearGradientBrush>

업데이트: 다른 창을 "삭제"하기 위해 양식을 닫을 때 이 이벤트 핸들러를 추가했습니다.

    private void NotificationWindowClosed(object sender, EventArgs e)
    {
        foreach (Window window in System.Windows.Application.Current.Windows)
        {
            string windowName = window.GetType().Name;

            if (windowName.Equals("NotificationWindow") && window != this)
            {
                // Adjust any windows that were above this one to drop down
                if (window.Top < this.Top)
                {
                    window.Top = window.Top + this.ActualHeight;
                }
            }
        }
    }
public partial class NotificationWindow : Window
{
    DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
    public NotificationWindow()
        : base()
    {
        this.InitializeComponent();

        Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
        {
            var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
            var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
            var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));

            this.Left = corner.X - this.ActualWidth;
            this.Top = corner.Y - this.ActualHeight;
        }));
        timer.Interval = TimeSpan.FromSeconds(4d);
        timer.Tick += new EventHandler(timer_Tick);
    }
    public new void Show()
    {
        base.Show();
        timer.Start();
    }

    void timer_Tick(object sender, EventArgs e)
    {
        //set default result if necessary

        timer.Stop();
        this.Close();
    }

}

위의 코드는 정제된 버전 @Ray Burns 접근 방식입니다.시간 간격 코드가 추가되었습니다.알림 창이 4초 후에 닫히도록 합니다.

다음과 같이 창을 호출합니다.

NotificationWindow nfw = new NotificationWindow();
nfw.Show();

저는 위의 답변을 사용하여 좀 더 사용자 친화적인 알림 창을 설계했으며, 몇 가지 기술을 사용하여 알아냈습니다.다른 사람에게 도움이 될 경우를 대비해 공유합니다.:

  1. Added MouseEnter 이벤트 트리거 - 창 불투명도를 즉시 1로 설정하여 사용자가 창이 전체 보기로 페이드될 때까지 기다릴 필요가 없습니다.
  2. 사용자가 마우스를 창 밖으로 이동할 때 창 불투명도를 0으로 페이드하는 Mouse Leave 이벤트 트리거가 추가되었습니다.
  3. 알림 창을 숨기기 위해 창 불투명도를 즉시 0으로 설정하는 MouseUp(마우스 클릭) 이벤트 트리거가 추가되었습니다.
  4. 알림 창을 여러 번 표시() 및 숨기기()해야 하는 경우, 아래 방법은 다음 표시() 작업이 시작부터 시작되고 애니메이션에 글리치가 없도록 끝에 있는 스토리보드도 재설정합니다.

XAML:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="ToastNotificationWindow"
    Title="Notification Popup"
    Width="480"
    Height="140"
    WindowStyle="None"
    AllowsTransparency="True"
    Background="Transparent"
    BorderThickness="0"
    Topmost="True"
>


    <Grid Background="Transparent" Name="ToastWindowGrid" RenderTransformOrigin="0,1">

        <Border Name="ToastWindowBorder" BorderThickness="0" Background="#333333">

            <StackPanel Name="ToastWindowStackPanel" Margin="10" Orientation="Horizontal">

                <Image Name="ToastLogo" Width="100" Height="100" Source="D:\Development\ToastNotification\resources\Logo-x100.png"/>

                <StackPanel Name="ToastMessageStackPanel" Width="359">

                    <TextBox Name="ToastTitleTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="White" FontSize="20" Text="Toast Title" FontWeight="Bold" HorizontalContentAlignment="Center" Width="Auto" HorizontalAlignment="Stretch" IsHitTestVisible="False"/>

                    <TextBox Name="ToastMessageTextBox" Margin="5" MaxWidth="340" Background="#333333" BorderThickness="0" IsReadOnly="True" Foreground="LightGray" FontSize="16" Text="Toast title message. Click to start something." HorizontalContentAlignment="Left" TextWrapping="Wrap" IsHitTestVisible="False"/>

                </StackPanel>

            </StackPanel>

        </Border>

        <Grid.Triggers>

            <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                <EventTrigger.Actions>
                    <BeginStoryboard Name="StoryboardLoad">
                        <Storyboard Name="ToastAnimationStoryboard">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" FillBehavior="HoldEnd">
                                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                                <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                            </DoubleAnimationUsingKeyFrames>
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
                                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="0"/>
                                <SplineDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
                            </DoubleAnimationUsingKeyFrames>
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
                                <SplineDoubleKeyFrame KeyTime="0:0:20" Value="1"/>
                                <SplineDoubleKeyFrame KeyTime="0:0:23" Value="0"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>

            <EventTrigger RoutedEvent="Mouse.MouseEnter">
                <EventTrigger.Actions>
                    <StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
                    <BeginStoryboard Name="StoryboardMouseEnterFadeIn">
                        <Storyboard Name="ToastAnimationStoryboardMouseEnterFadeIn">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
                                <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="1"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>

            <EventTrigger RoutedEvent="Mouse.MouseLeave">
                <EventTrigger.Actions>
                    <RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
                    <BeginStoryboard Name="StoryboardMouseLeaveFadeOut">
                        <Storyboard Name="ToastAnimationStoryboardMouseLeaveFadeOut">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
                                <SplineDoubleKeyFrame KeyTime="0:0:0" Value="1"/>
                                <SplineDoubleKeyFrame KeyTime="0:0:3" Value="0"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>

            <EventTrigger RoutedEvent="Mouse.MouseUp">
                <EventTrigger.Actions>
                    <StopStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/>
                    <RemoveStoryboard BeginStoryboardName="StoryboardMouseEnterFadeIn"/>
                    <StopStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
                    <RemoveStoryboard BeginStoryboardName="StoryboardMouseLeaveFadeOut"/>
                    <BeginStoryboard Name="StoryboardMouseClickFadeOut">
                        <Storyboard Name="ToastAnimationStoryboardMouseClickFadeOut">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" FillBehavior="HoldEnd">
                                <DiscreteDoubleKeyFrame KeyTime="0:0:0.1" Value="0"/>
                            </DoubleAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                    <SeekStoryboard BeginStoryboardName="StoryboardLoad"/>
                    <PauseStoryboard BeginStoryboardName="StoryboardLoad"/>
                    </EventTrigger.Actions>
            </EventTrigger>
        </Grid.Triggers>

        <Grid.RenderTransform>
            <ScaleTransform ScaleY="1" />
        </Grid.RenderTransform>

    </Grid>

</Window>

코드 배경:

using System;
using System.Windows;
using System.Windows.Threading;

public partial class ToastNotificationWindow
{
    public ToastNotificationWindow()
    {
        InitializeComponent();

        Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
        {
            var workingArea = System.Windows.SystemParameters.WorkArea;
            var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
            var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom));

            this.Left = corner.X - this.ActualWidth - 10;
            this.Top = corner.Y - this.ActualHeight;
        }));
    }
}
NotifyIcon notifyIcon = new NotifyIcon();
Stream iconStream = System.Windows.Application.GetResourceStream(new Uri("pack://application:,,,/Assets/ic_instant_note_tray.ico")).Stream;
notifyIcon.Icon = new System.Drawing.Icon(iconStream);
notifyIcon.Text = string.Format(Properties.Resources.InstantNoteAppName, Constants.Application_Name);
notifyIcon.Visible = true;
notifyIcon.ShowBalloonTip(5000, "tooltiptitle", "tipMessage", ToolTipIcon.Info);
notifyIcon.Visible = false;
notifyIcon.Dispose();

호출 스레드는 시스템에서 다음 코드를 작성하는 동안 많은 UI 구성 요소가 이를 필요로 하기 때문에 sta여야 합니다.timers.timer 경과 이벤트

Window1 notifyWin = new Window1();
bool? isOpen = notifyWin.ShowDialog();
if (isOpen != null && isOpen == true)
{
     notifyWin.Close();
}
System.Threading.Thread.Sleep(1000);
notifyWin.ShowDialog();

창 1 생성자 아래:

public Window1()
{
    InitializeComponent();

    Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() => { 
        var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea; 
        var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice; 
        var corner = transform.Transform(new Point(workingArea.Right, workingArea.Bottom)); 
        this.Left = corner.X - this.ActualWidth - 100; 
        this.Top = corner.Y - this.ActualHeight; 
    })); 
}

언급URL : https://stackoverflow.com/questions/3034741/create-popup-toaster-notifications-in-windows-with-net

반응형