天天看點

WPF 實作 Message 消息提醒控件

作者:opendotnet

WPF 實作 Message 消息提醒控件

控 件:Message

作 者:WPFDevelopersOrg - 驚鏵

原文連結[1]:https://github.com/WPFDevelopersOrg/WPFDevelopers

  • 架構使用

    .NET4 至 .NET6

  • Visual Studio 2022

    ;
  • 接着上一篇
WPF 實作 Message 消息提醒控件

1)新增

MessageListBoxItem.cs

代碼如下:

  • 新增了名為

    MessageType

    的依賴屬性,類型為

    MessageBoxImage

    ,預設值為

    MessageBoxImage.Information

  • 新增了名為

    IsCenter

    的依賴屬性,預設值為

    false

    ,為

    true

    則内容居中顯示。
using System.Windows;
using System.Windows.Controls;

namespace WPFDevelopers.Controls
{
public class MessageListBoxItem : ListBoxItem
 {
public MessageBoxImage MessageType
 {
get { return (MessageBoxImage)GetValue(MessageTypeProperty); }
set { SetValue(MessageTypeProperty, value); }
 }
public static readonly DependencyProperty MessageTypeProperty =
 DependencyProperty.Register("MessageType", typeof(MessageBoxImage), typeof(MessageListBoxItem), new PropertyMetadata(MessageBoxImage.Information));

public bool IsCenter
 {
get { return (bool)GetValue(IsCenterProperty); }
set { SetValue(IsCenterProperty, value); }
 }

public static readonly DependencyProperty IsCenterProperty =
 DependencyProperty.Register("IsCenter", typeof(bool), typeof(MessageListBoxItem), new PropertyMetadata(false));


 }
}

           

2)新增

MessageListBox.cs

代碼如下:

  • 自定義

    MessageListBox

    繼承

    ListBox

    ,其中重寫了兩個方法:

    IsItemItsOwnContainerOverride

    GetContainerForItemOverride

  1. GetContainerForItemOverride

    方法用于建立并傳回一個新的容器對象,用于承載清單框中的項。在這裡,它建立并傳回一個

    MessageListBoxItem

    的執行個體作為容器對象。
  1. IsItemItsOwnContainerOverride

    方法用于确定給定的項是否應該作為其自己的容器。在這裡,它檢查傳入的

    item

    對象是否是

    MessageListBoxItem

    的執行個體。如果是,則傳回

    true

    ,表示該項是其自己的容器;否則,傳回

    false

using System.Windows;
using System.Windows.Controls;

namespace WPFDevelopers.Controls
{
public class MessageListBox : ListBox
 {
protected override bool IsItemItsOwnContainerOverride(object item)
 {
return item is MessageListBoxItem;
 }

protected override DependencyObject GetContainerForItemOverride()
 {
return new MessageListBoxItem();
 }
 }
}

           

3)新增

MessageAdorner.cs

代碼如下:

  • MessageAdorner

    是一個繼承自

    Adorner

    的自定義類。它用于在裝飾元素上顯示消息的附加裝飾器。
  • 構造函數

    MessageAdorner(UIElement adornedElement)

    接受一個

    UIElement

    類型的參數,作為要進行裝飾的元素。
  • Push

    方法用于将消息添加到裝飾器中。它接受消息内容、消息類型和是否居中顯示的參數。如果裝飾器尚未建立,則會建立一個

    MessageListBox

    并将其設定為裝飾器的子元素。然後,根據傳入的參數建立一個新的

    MessageListBoxItem

    ,并将其插入到清單框的頂部。
  • Child

    屬性用于擷取或設定裝飾器的子元素。當設定子元素時,會将子元素添加到裝飾器的可視化子元素集合中;當子元素為 時,會從可視化子元素集合中移除子元素。
  • VisualChildrenCount

    屬性傳回裝飾器的可視化子元素數量。
  • ArrangeOverride

    方法用于調整裝飾器的布局。在這裡,根據裝飾元素的寬度和子元素的期望大小,計算出子元素的水準位置,并将其排列在裝飾器的頂部。
  • GetVisualChild

    方法用于擷取指定索引處的可視化子元素。在這裡,如果索引為0且子元素不為 ,則傳回子元素;否則,調用基類的方法傳回對應索引的可視化子元素。
using System.Windows;
using System.Windows.Documents;
using System.Windows.Media;

namespace WPFDevelopers.Controls
{
public class MessageAdorner : Adorner
 {
private MessageListBox listBox;
private UIElement _child;
private FrameworkElement adornedElement;
public MessageAdorner(UIElement adornedElement) : base(adornedElement)
 {
this.adornedElement = adornedElement as FrameworkElement;
 }

public void Push(string message, MessageBoxImage type = MessageBoxImage.Information, bool center = false)
 {
if (listBox == )
 {
 listBox = new MessageListBox();
 Child = listBox;
 }
var mItem = new MessageListBoxItem { Content = message, MessageType = type, IsCenter = center };
 listBox.Items.Insert(0, mItem);
 }
public UIElement Child
 {
get => _child;
set
 {
if (value == )
 {
 RemoveVisualChild(_child);
 _child = value;
return;
 }
 AddVisualChild(value);
 _child = value;
 }
 }
protected override int VisualChildrenCount
 {
get
 {
return _child !=  ? 1 : 0;
 }
 }

protected override Size ArrangeOverride(Size finalSize)
 {
var x = (adornedElement.ActualWidth - _child.DesiredSize.Width) / 2;
 _child.Arrange(new Rect(new Point(x, 0), _child.DesiredSize));
return finalSize;
 }

protected override Visual GetVisualChild(int index)
 {
if (index == 0 && _child != ) return _child;
return base.GetVisualChild(index);
 }
 }
}

           

4)新增

Message.cs

代碼如下:

  • CreateMessageAdorner

    方法用于建立消息裝飾器

    MessageAdorner

    。它接受一些參數,如視窗所有者

    Window owner

    、消息内容

    string message

    、消息類型

    MessageBoxImage type

    和是否居中顯示

    bool center

    。在方法内部,它首先檢查是否已經存在消息裝飾器執行個體

    messageAdorner

    ,如果存在,則調用裝飾器的

    Push

    方法将新的消息添加到裝飾器中,并直接傳回。如果不存在消息裝飾器執行個體,則根據提供的視窗所有者或擷取預設視窗

    ControlsHelper.GetDefaultWindow()

    ,然後擷取對應的裝飾層

    AdornerLayer

    。如果裝飾層為空,則抛出異常。接着,建立一個新的消息裝飾器執行個體,并将其添加到裝飾層中。最後,調用裝飾器的

    Push

    方法将消息添加到裝飾器中。
  • Push

    方法是一個擴充方法,用于在指定的視窗上顯示消息。它接受視窗對象

    Window owner

    作為第一個參數,以及其他參數,如消息内容

    string message

    、消息類型

    MessageBoxImage type

    和是否居中顯示

    bool center

    。在方法内部,它調用

    CreateMessageAdorner

    方法,并傳遞相應的參數。
  • 另一個重載的

    Push

    方法是直接在靜态上下文中調用的。它接受消息内容

    string message

    、消息類型

    MessageBoxImage type

    和是否居中顯示

    bool center

    作為參數,并調用

    CreateMessageAdorner

    方法。
using System;
using System.Linq;
using System.Windows;
using WPFDevelopers.Helpers;

namespace WPFDevelopers.Controls
{
public static class Message
 {
private static MessageAdorner messageAdorner;

static void CreateMessageAdorner(Window owner = , string message = , MessageBoxImage type = MessageBoxImage.Information, bool center = false)
 {
try
 {
if (messageAdorner != )
 {
 messageAdorner.Push(message, type, center);
return;
 }
if (owner == )
 owner = ControlsHelper.GetDefaultWindow();
var layer = ControlsHelper.GetAdornerLayer(owner);
if (layer == )
throw new Exception("not AdornerLayer is ");
 messageAdorner = new MessageAdorner(layer);
 layer.Add(messageAdorner);
 messageAdorner.Push(message, type, center);
 }
catch (Exception)
 {
throw;
 }
 }
public static void Push(this Window owner, string message, MessageBoxImage type = MessageBoxImage.Information, bool center = false)
 {
 CreateMessageAdorner(owner, message, type, center);
 }
public static void Push(string message, MessageBoxImage type = MessageBoxImage.Information, bool center = false)
 {
 CreateMessageAdorner(message: message, type: type, center: center);
 }
 }
}

           

5)新增

MessageListBoxItem.xaml

代碼如下:

  • XAML

    代碼定義一個名為

    Storyboard_Loaded

    的故事闆(

    Storyboard

    ),其中包含了四個

    DoubleAnimation

    動畫
    • 第一個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      Y

      軸縮放屬性

      ScaleTransform.ScaleY

      從 變化到

      1

      ,持續時間為

      0.2

      秒。這會使

      PART_SmallPanel

      在加載時以漸變的方式顯示出來。
    • 第二個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      的不透明度屬性

      Opacity

      0.1

      變化到

      1

      ,持續時間為

      0.2

      秒。這也是為了使

      PART_SmallPanel

      在加載時以漸變的方式顯示出來。
    • 第三個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      Y

      軸縮放屬性

      ScaleTransform.ScaleY

      1

      變化到 ,持續時間為

      0.2

      秒。這個動畫在

      10

      秒後開始執行,會使

      PART_SmallPanel

      以漸變的方式從可見狀态變為不可見狀态。
    • 第四個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      的不透明度屬性

      Opacity

      1

      變化到 ,持續時間為

      0.2

      秒。同樣地,在10秒後開始執行,使

      PART_SmallPanel

      以漸變的方式從可見狀态變為不可見狀态。
  • 定義一個名為

    Storyboard_Close

    的故事闆

    Storyboard

    ,其中包含了兩個

    DoubleAnimation

    動畫。
    • 第一個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      的Y軸縮放屬性

      (UIElement.RenderTransform).(ScaleTransform.ScaleY)

      1

      變化到 ,持續時間為

      0.2

      秒。這會使

      PART_SmallPanel

      以漸變的方式從可見狀态變為不可見狀态。
    • 第二個

      DoubleAnimation

      動畫将目标元素

      PART_SmallPanel

      的不透明度屬性(

      Opacity

      )從

      1

      變化到0,持續時間為 秒。該動畫在

      0.2

      秒後開始執行,使

      PART_SmallPanel

      以立即消失的方式從可見狀态變為不可見狀态。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Controls"
xmlns:helpers="clr-namespace:WPFDevelopers.Helpers">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Basic/ControlBasic.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style
x:Key="WD.MessageListBoxItem"
BasedOn="{StaticResource WD.ControlBasicStyle}"
TargetType="{x:Type controls:MessageListBoxItem}">
<Setter Property="FontSize" Value="{DynamicResource WD.NormalFontSize}" />
<Setter Property="Foreground" Value="{DynamicResource WD.RegularTextSolidColorBrush}" />
<Setter Property="Background" Value="{DynamicResource WD.BackgroundSolidColorBrush}" />
<Setter Property="helpers:ListBoxItemExtensions.AutoRemoveOnOpacityZero" Value="True" />
<Setter Property="Width" Value="300" />
<Setter Property="Height" Value="Auto" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="4,2" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MessageListBoxItem}">
<ControlTemplate.Resources>
<Storyboard x:Key="Storyboard_Close">
<DoubleAnimation
Storyboard.TargetName="PART_SmallPanel"
Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
From="1"
To="0"
Duration="0:0:0.2" />
<DoubleAnimation
BeginTime="0:0:0.2"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0" />
</Storyboard>
<Storyboard x:Key="Storyboard_Loaded">
<DoubleAnimation
Storyboard.TargetName="PART_SmallPanel"
Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
From="0"
To="1"
Duration="0:0:0.2" />
<DoubleAnimation
Storyboard.TargetName="PART_SmallPanel"
Storyboard.TargetProperty="Opacity"
From=".1"
To="1"
Duration="0:0:0.2" />
<DoubleAnimation
BeginTime="0:0:10"
Storyboard.TargetName="PART_SmallPanel"
Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)"
From="1"
To="0"
Duration="0:0:0.2" />
<DoubleAnimation
BeginTime="0:0:10"
Storyboard.TargetProperty="Opacity"
From="1"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</ControlTemplate.Resources>
<controls:SmallPanel
x:Name="PART_SmallPanel"
Margin="{TemplateBinding Margin}"
RenderTransformOrigin=".5,0">
<controls:SmallPanel.RenderTransform>
<ScaleTransform />
</controls:SmallPanel.RenderTransform>
<Border
x:Name="PART_Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{Binding Path=(helpers:ElementHelper.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
Effect="{StaticResource WD.NormalShadowDepth}"
SnapsToDevicePixels="True"
UseLayoutRounding="True" />
<Border Padding="{TemplateBinding Padding}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<DockPanel x:Name="PART_DockPanel" Grid.Column="0">
<Path
x:Name="PART_Path"
Width="15"
Height="15"
Data="{StaticResource WD.InformationGeometry}"
Fill="{DynamicResource WD.PrimaryNormalSolidColorBrush}"
Stretch="Fill" />
<TextBlock
Grid.Column="1"
Margin="5,0,0,0"
VerticalAlignment="Center"
FontSize="{TemplateBinding FontSize}"
Foreground="{TemplateBinding Foreground}"
Text="{TemplateBinding Content}"
TextWrapping="Wrap" />
</DockPanel>
<Button
x:Name="PART_CloseButton"
Grid.Column="2"
Width="30"
Height="28"
Padding="0"
HorizontalAlignment="Right"
Style="{StaticResource WD.PathButton}">
<Path
Width="10"
Height="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="{DynamicResource WD.WindowCloseGeometry}"
Fill="{DynamicResource WD.SecondaryTextSolidColorBrush}"
Stretch="Fill" />
</Button>
</Grid>
</Border>
</controls:SmallPanel>
<ControlTemplate.Triggers>
<Trigger Property="MessageType" Value="Warning">
<Setter TargetName="PART_Path" Property="Data" Value="{StaticResource WD.WarningGeometry}" />
<Setter TargetName="PART_Path" Property="Fill" Value="{StaticResource WD.WarningSolidColorBrush}" />
<Setter Property="Foreground" Value="{StaticResource WD.WarningSolidColorBrush}" />
</Trigger>
<Trigger Property="MessageType" Value="Error">
<Setter TargetName="PART_Path" Property="Data" Value="{StaticResource WD.ErrorGeometry}" />
<Setter TargetName="PART_Path" Property="Fill" Value="{StaticResource WD.DangerSolidColorBrush}" />
<Setter Property="Foreground" Value="{StaticResource WD.DangerSolidColorBrush}" />
</Trigger>
<Trigger Property="MessageType" Value="Information">
<Setter TargetName="PART_Path" Property="Data" Value="{StaticResource WD.InformationGeometry}" />
<Setter TargetName="PART_Path" Property="Fill" Value="{StaticResource WD.SuccessSolidColorBrush}" />
<Setter Property="Foreground" Value="{StaticResource WD.SuccessSolidColorBrush}" />
</Trigger>
<Trigger Property="MessageType" Value="Question">
<Setter TargetName="PART_Path" Property="Data" Value="{StaticResource WD.QuestionGeometry}" />
<Setter TargetName="PART_Path" Property="Fill" Value="{StaticResource WD.NormalSolidColorBrush}" />
<Setter Property="Foreground" Value="{StaticResource WD.NormalSolidColorBrush}" />
</Trigger>
<Trigger Property="IsCenter" Value="True">
<Setter TargetName="PART_DockPanel" Property="HorizontalAlignment" Value="Center" />
</Trigger>
<EventTrigger RoutedEvent="Button.Click" SourceName="PART_CloseButton">
<BeginStoryboard x:Name="BeginStoryboardClose" Storyboard="{StaticResource Storyboard_Close}" />
</EventTrigger>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard x:Name="BeginStoryboardLoaded" Storyboard="{StaticResource Storyboard_Loaded}" />
</EventTrigger>
<EventTrigger RoutedEvent="Unloaded">
<StopStoryboard BeginStoryboardName="BeginStoryboardLoaded" />
<StopStoryboard BeginStoryboardName="BeginStoryboardClose" />
</EventTrigger>
<Trigger Property="Opacity" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

<Style x:Key="WD.MessageListBox" TargetType="{x:Type controls:MessageListBox}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
</Style>

<Style BasedOn="{StaticResource WD.MessageListBoxItem}" TargetType="{x:Type controls:MessageListBoxItem}" />
<Style BasedOn="{StaticResource WD.MessageListBox}" TargetType="{x:Type controls:MessageListBox}" />

</ResourceDictionary>
           

6)新增

ListBoxItemExtensions

代碼如下:

  • 在方法内部,首先将

    sender

    轉換為

    ListBoxItem

    類型,并進行空引用檢查。然後,建立一個綁定對象

    binding

    ,将其源設定為目前的

    item

    ,并将綁定模式設定為單向。
  • 接下來,使用

    DependencyPropertyDescriptor

    UIElement.OpacityProperty

    屬性擷取一個依賴屬性描述符

    dpd

    。通過調用

    AddValueChanged

    方法,将一個值更改事件的處理程式添加到

    item

    上。當

    item

    的不透明度

    Opacity

    小于0.1時,該處理程式将執行以下操作:
    • 擷取父級

      ItemsControl

      ,即包含

      item

      的清單框。
    • 如果找到了父級

      ItemsControl

      ,則使用

      ItemContainerGenerator

      item

      擷取關聯的資料項(

      selectedItem

      )。
    • 從父級

      ItemsControl

      Items

      集合中移除

      selectedItem

    • 調用父級

      ItemsControl

      Refresh

      方法,以重新整理清單框的顯示。
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WPFDevelopers.Helpers
{
public static class ListBoxItemExtensions
 {
public static readonly DependencyProperty AutoRemoveOnOpacityZeroProperty =
 DependencyProperty.RegisterAttached("AutoRemoveOnOpacityZero", typeof(bool), typeof(ListBoxItemExtensions), new PropertyMetadata(false, OnAutoRemoveOnOpacityZeroChanged));

public static bool GetAutoRemoveOnOpacityZero(DependencyObject obj)
 {
return (bool)obj.GetValue(AutoRemoveOnOpacityZeroProperty);
 }

public static void SetAutoRemoveOnOpacityZero(DependencyObject obj, bool value)
 {
 obj.SetValue(AutoRemoveOnOpacityZeroProperty, value);
 }

private static void OnAutoRemoveOnOpacityZeroChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
 {
 ListBoxItem item = obj as ListBoxItem;
if (item != )
 {
if ((bool)e.NewValue)
 item.Loaded += Item_Loaded;
else
 item.Loaded -= Item_Loaded;
 }
 }

private static void Item_Loaded(object sender, RoutedEventArgs e)
 {
var item = sender as ListBoxItem;
if (item != )
 {
var binding = new Binding("Opacity");
 binding.Source = item;
 binding.Mode = BindingMode.OneWay;

var dpd = DependencyPropertyDescriptor.FromProperty(UIElement.OpacityProperty, typeof(UIElement));
 dpd.AddValueChanged(item, (s, args) =>
 {
if (item.Opacity < 0.1)
 {
var parent = ItemsControl.ItemsControlFromItemContainer(item);
if (parent != )
 {
object selectedItem = parent.ItemContainerGenerator.ItemFromContainer(item);
 parent.Items.Remove(selectedItem);
 parent.Items.Refresh();
 }
 }
 });
 }
 }
 }
}
           

7)新增

示例

代碼如下:

<wd:Window
x:Class="MessageSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MessageSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wd="https://github.com/WPFDevelopersOrg/WPFDevelopers"
Title="WPFDevelopers - Message"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<StackPanel
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Orientation="Horizontal">
<Button
Click="AddButton_Click"
Content="Info Message"
Style="{StaticResource WD.SuccessPrimaryButton}"
Tag="Info" />
<Button
Click="AddButton_Click"
Content="Error Message"
Style="{StaticResource WD.DangerPrimaryButton}"
Tag="Error" />
<Button
Click="AddButton_Click"
Content="Warning Message"
Style="{StaticResource WD.WarningPrimaryButton}"
Tag="Warning" />
<Button
Click="AddButton_Click"
Content="Question Message"
Style="{StaticResource WD.PrimaryButton}"
Tag="Question" />
<Button
Click="AddButton_Click"
Content="Long Message"
Style="{StaticResource WD.SuccessPrimaryButton}"
Tag="Long" />
</StackPanel>
</Grid>
</wd:Window>

           

8)

示例

代碼如下:

private void AddButton_Click(object sender, RoutedEventArgs e)
 {
var btn = sender as Button;
switch (btn.Tag)
 {
case "Info":
 Message.Push(App.Current.MainWindow, "This is a info message", MessageBoxImage.Information);
break;
case "Error":
 Message.Push("This is a error message", MessageBoxImage.Error, true);
break;
case "Warning":
 Message.Push("This is a warning message", MessageBoxImage.Warning, true);
break;
case "Question":
 Message.Push("This is a question message", MessageBoxImage.Question);
break;
default:
 Message.Push("這是一條很長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長長消息", MessageBoxImage.Information);
break;
 }

 }
           
WPF 實作 Message 消息提醒控件

碼雲[2]

參考資料

[1]

原文連結: https://github.com/WPFDevelopersOrg/WPFDevelopers

[2]

碼雲: https://github.com/WPFDevelopersOrg/WPFDevelopers

繼續閱讀