Browse Source

完善消息中心本地逻辑

master
shanji 1 year ago
parent
commit
73873d7f9d
  1. 9
      QYMessageCenter.Business/MessageBusiness.cs
  2. 8
      QYMessageCenter.Client/App.xaml
  3. 2
      QYMessageCenter.Client/App.xaml.cs
  4. 42
      QYMessageCenter.Client/Converters/AppCodeConverter.cs
  5. 105
      QYMessageCenter.Client/GlobalContext.cs
  6. 3
      QYMessageCenter.Client/MainWindow.xaml
  7. 17
      QYMessageCenter.Client/MainWindow.xaml.cs
  8. 71
      QYMessageCenter.Client/Models/Msg/Message.cs
  9. 25
      QYMessageCenter.Client/Models/Msg/Message_PJZS_SHANGJIASHIBAI.cs
  10. 64
      QYMessageCenter.Client/PopupManager.cs
  11. 122
      QYMessageCenter.Client/PopupWindow.xaml
  12. 47
      QYMessageCenter.Client/PopupWindow.xaml.cs
  13. 2
      QYMessageCenter.Client/QYMessageCenter.Client.csproj
  14. 19
      QYMessageCenter.Client/Resources/Themes/Generic.xaml
  15. 30
      QYMessageCenter.Client/TemplateSelector/MessageTemplateSelector.cs
  16. 99
      QYMessageCenter.Client/static/index.html
  17. 8
      QYMessageCenter.Model/DB/QYNotification.cs
  18. 5
      QYMessageCenter.Model/DTO/Message/SendMessageRequest.cs
  19. 9
      QYMessageCenter.sln
  20. 5
      QYMessageCenter/SWDescription.txt
  21. 10
      SJ.Controls/AssemblyInfo.cs
  22. 705
      SJ.Controls/BAsyncImage.cs
  23. 72
      SJ.Controls/BButton.cs
  24. 210
      SJ.Controls/BTextBox.cs
  25. 109
      SJ.Controls/BTextBoxAnimation.cs
  26. 178
      SJ.Controls/BWindow.cs
  27. 71
      SJ.Controls/Extensions/VisualTreeExtension.cs
  28. 188
      SJ.Controls/Helpers/StoryboardHelper.cs
  29. 24
      SJ.Controls/PageControl.xaml
  30. 137
      SJ.Controls/PageControl.xaml.cs
  31. 56
      SJ.Controls/RoundWaitProgress.xaml
  32. 180
      SJ.Controls/RoundWaitProgress.xaml.cs
  33. 17
      SJ.Controls/SJ.Controls.csproj
  34. 377
      SJ.Controls/Themes/Generic.xaml

9
QYMessageCenter.Business/MessageBusiness.cs

@ -1,8 +1,11 @@
using BBWYB.Server.Business;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using QYMessageCenter.Common.Extensions;
using QYMessageCenter.Common.Http;
using QYMessageCenter.Common.Log;
using QYMessageCenter.Common.Models;
using QYMessageCenter.Model.DB;
using QYMessageCenter.Model.DTO;
using System.Configuration;
using System.Threading.Channels;
@ -25,12 +28,16 @@ namespace QYMessageCenter.Business
/// <param name="request"></param>
public void Send(SendMessageRequest request)
{
var msg = request.Map<QYNotification>();
msg.Id = idGenerator.NewLong();
msg.CreateTime = DateTime.Now;
#region 调用goeasy
var goeasy_httpResult = restApiService.SendRequest("https://rest-hz.goeasy.io/", "v2/pubsub/publish", new
{
appkey = Key,
channel = request.Channel,
content = request.Content
content = JsonConvert.SerializeObject(msg)
}, null, HttpMethod.Post);
if (goeasy_httpResult.StatusCode != System.Net.HttpStatusCode.OK)

8
QYMessageCenter.Client/App.xaml

@ -2,8 +2,14 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:QYMessageCenter.Client"
xmlns:ctr="clr-namespace:QYMessageCenter.Client.Converters"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Resources/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ctr:AppCodeConverter x:Key="appCodeCtr"/>
</ResourceDictionary>
</Application.Resources>
</Application>

2
QYMessageCenter.Client/App.xaml.cs

@ -24,6 +24,8 @@ namespace QYMessageCenter.Client
#if DEBUG
//齐越山鸡
userToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxNTM1MzMwMzI4ODkyMTQ5NzYwIiwidGVhbUlkIjoiMTUxNjk3NDI1MDU0MjUwMTg4OCIsInNvblRlYW1JZHMiOiIxNDM2Mjg4NTAwMjM1MjQzNTIwIiwiZXhwIjoxNzI2MzAwNjY0fQ.hPSbgJEuTt0MLy_7YkSJX4rRG3drJAfso-5IS8ZlOkY";
//userToken="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMzk1NzA4MjA4NjU1MzcyMjg4IiwidGVhbUlkIjoiMTQzNjI4ODUwMDIzNTI0MzUyMCIsInNvblRlYW1JZHMiOiIxNDM2Mjg4NTAwMjM1MjQzNTIwIiwiZXhwIjoxNzQwMTM3NzY5fQ.Wq7IRvAkrVpn28Bj3ImG_KAg9MZ3C2Ux84vSjXyd0ZY"; //测试
#else
var tokenResult = ReadMMF();

42
QYMessageCenter.Client/Converters/AppCodeConverter.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
namespace QYMessageCenter.Client.Converters
{
public class AppCodeConverter : IValueConverter
{
private IDictionary<string, string> codeDic;
public AppCodeConverter()
{
codeDic = new Dictionary<string, string>()
{
{ "PJZS", "评价助手" },
{ "BBWYC", "步步为盈C端" },
{ "BBWYB", "步步为盈B端" },
{ "QK", "齐库" },
{ "LK", "良库" },
{ "SBF", "三板斧" },
{ "SN", "司南" },
};
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var appCode = value?.ToString() ?? string.Empty;
if (codeDic.TryGetValue(appCode, out var appName))
return appName;
else return "UnKnow App";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

105
QYMessageCenter.Client/GlobalContext.cs

@ -1,6 +1,11 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using QYMessageCenter.Client.Models;
using QYMessageCenter.Client.Models.Msg;
using QYMessageCenter.Common.Extensions;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace QYMessageCenter.Client
{
@ -8,6 +13,16 @@ namespace QYMessageCenter.Client
[ComVisible(true)]
public class GlobalContext
{
public GlobalContext()
{
messageHandleDic = new Dictionary<string, Action<Message>>()
{
{ "PJZS", OnReceiveMessageFromPJZS }
};
popupManager = new PopupManager();
}
public User User { get; set; }
public string UserToken { get; set; }
@ -16,5 +31,95 @@ namespace QYMessageCenter.Client
{
return JsonConvert.SerializeObject(User);
}
private IDictionary<string, Action<Message>> messageHandleDic;
private PopupManager popupManager;
public void OnError(string msg)
{
App.Current.Dispatcher.Invoke(() =>
{
MessageBox.Show(msg, "齐越消息中心");
});
}
public void OnReceiveMessage(string channel, string message)
{
try
{
var msg = JsonConvert.DeserializeObject<Message>(message);
if (!string.IsNullOrEmpty(msg.RecevierId) && !msg.RecevierId.Contains(User.Id.ToString()))
return; //忽略不是自己的消息
if (messageHandleDic.ContainsKey(msg.AppCode))
messageHandleDic[msg.AppCode](msg);
else
OnReceiveMessageFromUnknow(msg);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "齐越消息中心");
}
}
private void OnReceiveMessageFromPJZS(Message msg)
{
if (msg.CustomTypeCode == "SHANGJIASHIBAI")
{
var smsg = msg.Map<Message_PJZS_SHANGJIASHIBAI>();
var j = JObject.Parse(msg.Content);
smsg.ActivityName = j.Value<string>("ActivityName");
smsg.SpuLogo = j.Value<string>("SpuLogo");
smsg.MainProductSpu = j.Value<string>("MainProductSpu");
smsg.ErrorMsg = j.Value<string>("ErrorMsg");
App.Current.Dispatcher.BeginInvoke(() => popupManager.Show(smsg));
return;
}
App.Current.Dispatcher.BeginInvoke(() => popupManager.Show(msg));
}
private void OnReceiveMessageFromUnknow(Message msg)
{
//ReceiveMessageList.Insert(0, msg);
App.Current.Dispatcher.BeginInvoke(() => popupManager.Show(msg));
}
public void Test()
{
App.Current.Dispatcher.BeginInvoke((Action)delegate
{
popupManager.Show(new Message_PJZS_SHANGJIASHIBAI()
{
AppCode = "PJZS",
ShopId = "12899501",
CustomTypeCode = "SHANGJIASHIBAI",
Title = "信奉玩具专营店",
Content = JsonConvert.SerializeObject(new
{
}),
ActivityName = "小熊头枕腰靠",
MainProductSpu = "10025305065298",
SpuLogo = "https://img13.360buyimg.com/n9/s100x100_jfs/t1/235072/18/12198/94918/65b261a6F6de6ba33/23b803a34546427f.jpg",
ErrorMsg = "上架sku失败-com,jd.bk,saf.exception.SafJosException:销售属性排序冲冲突,同一顺序下不能有两个相同别名,别名:[柿柿如意发财果串-乔迁吊坠]顺序:[3]#8d306b629bae458faa58e9f5f0c883fb(Solution reference: https://jos.jd.com/commondoc?listld=171)"
});
});
App.Current.Dispatcher.BeginInvoke((Action)delegate
{
popupManager.Show(new Message()
{
AppCode = "PJZS",
ShopId = "12899501",
Title = "信奉玩具专营店",
Content = "上架sku失败-com,jd.bk,saf.exception.SafJosException:这是一个普通模板消息",
});
});
}
}
}

3
QYMessageCenter.Client/MainWindow.xaml

@ -6,7 +6,8 @@
xmlns:local="clr-namespace:QYMessageCenter.Client"
mc:Ignorable="d"
ShowInTaskbar="False"
Title="齐越消息中心" Height="450" Width="800">
WindowStartupLocation="CenterScreen"
Title="齐越消息中心" Height="100" Width="300">
<Grid x:Name="grid">
<TextBlock Text="正在初始化齐越消息中心" HorizontalAlignment="Center" VerticalAlignment="Center" Panel.ZIndex="2"/>
</Grid>

17
QYMessageCenter.Client/MainWindow.xaml.cs

@ -7,6 +7,7 @@ using QYMessageCenter.Common.Extensions;
using System.IO;
using System.Reflection;
using System.Text;
using System.Transactions;
using System.Windows;
using io = System.IO;
namespace QYMessageCenter.Client
@ -45,17 +46,29 @@ namespace QYMessageCenter.Client
var wb2Setting = CoreWebView2Environment.CreateAsync(userDataFolder: io.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "QYMessageCenter.Client")).Result;
wb2.CoreWebView2InitializationCompleted += Wb2_CoreWebView2InitializationCompleted;
await wb2.EnsureCoreWebView2Async(wb2Setting);
wb2.CoreWebView2.PermissionRequested += CoreWebView2_PermissionRequested;
//wb2.CoreWebView2.NavigateToString(html);
}
private void CoreWebView2_PermissionRequested(object? sender, CoreWebView2PermissionRequestedEventArgs e)
{
e.State = CoreWebView2PermissionState.Allow;
}
private void Wb2_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e)
{
Login();
wb2.CoreWebView2.AddHostObjectToScript("qymsgcenter", this.globalContext);
var html = File.ReadAllText(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "static", "index.html"), Encoding.UTF8);
wb2.CoreWebView2.NavigateToString(html);
//var html = File.ReadAllText(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "static", "index.html"), Encoding.UTF8);
//wb2.CoreWebView2.NavigateToString(html);
var htmlPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "static", "index.html");
wb2.CoreWebView2.Navigate(htmlPath);
this.Visibility = Visibility.Collapsed;
//globalContext.Test();
}
private void Login()

71
QYMessageCenter.Client/Models/Msg/Message.cs

@ -0,0 +1,71 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace QYMessageCenter.Client.Models
{
public class Message : ObservableObject
{
private string appCode;
private string title;
private string content;
public long Id { get; set; }
/// <summary>
/// 消息频道
/// </summary>
public string Channel { get; set; }
/// <summary>
/// 消息所属团队Id
/// </summary>
public string TeamId { get; set; }
/// <summary>
/// 消息所属店铺Id
/// </summary>
public string ShopId { get; set; }
public DateTime? CreateTime { get; set; }
/// <summary>
/// 自定义类型编码
/// </summary>
public string CustomTypeCode { get; set; }
/// <summary>
/// 已读人员
/// </summary>
public string ReaderId { get; set; }
/// <summary>
/// 发送人
/// </summary>
public string SenderId { get; set; }
/// <summary>
/// 接收人(可空)
/// </summary>
public string RecevierId { get; set; }
/// <summary>
/// 是否为Json消息, 解析规则参考CustomTypeCode的约定
/// </summary>
public bool IsJsonMsg { get; set; }
/// <summary>
/// 消息所属应用
/// </summary>
public string AppCode { get => appCode; set { SetProperty(ref appCode, value); } }
/// <summary>
/// 消息标题
/// </summary>
public string Title { get => title; set { SetProperty(ref title, value); } }
/// <summary>
/// 消息内容
/// </summary>
public string Content { get => content; set { SetProperty(ref content, value); } }
}
}

25
QYMessageCenter.Client/Models/Msg/Message_PJZS_SHANGJIASHIBAI.cs

@ -0,0 +1,25 @@
namespace QYMessageCenter.Client.Models.Msg
{
/// <summary>
/// 消息_评价助手_上架失败
/// </summary>
public class Message_PJZS_SHANGJIASHIBAI : Message
{
private string activityName;
private string spuLogo;
private string mainProductSpu;
private string errorMsg;
public string ActivityName { get => activityName; set { SetProperty(ref activityName, value); } }
public string SpuLogo { get => spuLogo; set { SetProperty(ref spuLogo, value); } }
public string MainProductSpu { get => mainProductSpu; set { SetProperty(ref mainProductSpu, value); } }
public string ErrorMsg { get => errorMsg; set { SetProperty(ref errorMsg, value); } }
}
}

64
QYMessageCenter.Client/PopupManager.cs

@ -0,0 +1,64 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using QYMessageCenter.Client.Models;
namespace QYMessageCenter.Client
{
public class PopupManager
{
private List<Message> MessageList { get; set; }
private PopupWindow pw;
public PopupManager()
{
MessageList = new List<Message>();
WeakReferenceMessenger.Default.Register<MVVMMessage_ShowNext>(this, (o, x) => ShowNext());
}
public void Show(Message msg)
{
MessageList.Insert(0, msg);
Show();
}
private void Show()
{
var msg = MessageList[0];
if (pw != null)
{
pw.RefreshMsg(msg);
}
else
{
pw = new PopupWindow(msg);
pw.Closed += Pw_Closed;
pw.Show();
}
}
private void Pw_Closed(object? sender, EventArgs e)
{
pw = null;
MessageList.Clear();
}
public void ShowNext()
{
if (MessageList.Count() > 0)
MessageList.RemoveAt(0);
if (MessageList.Count() == 0)
pw.Close();
else
Show();
}
}
public class MVVMMessage_ShowNext : ValueChangedMessage<object>
{
public MVVMMessage_ShowNext(object value) : base(value)
{
}
}
}

122
QYMessageCenter.Client/PopupWindow.xaml

@ -0,0 +1,122 @@
<c:BWindow x:Class="QYMessageCenter.Client.PopupWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:QYMessageCenter.Client"
xmlns:c="clr-namespace:SJ.Controls;assembly=SJ.Controls"
xmlns:templateSelector="clr-namespace:QYMessageCenter.Client.TemplateSelector"
mc:Ignorable="d"
Title="PopupWindow" Height="260" Width="350"
MinButtonVisibility="Collapsed"
MaxButtonVisibility="Collapsed"
CloseButtonVisibility="Collapsed"
Style="{StaticResource bwstyle}">
<Window.Resources>
<SolidColorBrush x:Key="Border.Brush" Color="#D7D7D7"/>
<SolidColorBrush x:Key="Border.Background" Color="#8080FF"/>
<SolidColorBrush x:Key="Text.Link.Color" Color="#02A7F0"/>
<SolidColorBrush x:Key="Text.Property.Color" Color="Gray"/>
<Style x:Key="middleTextBlock" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="LinkButton" TargetType="{x:Type c:BButton}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource Text.Link.Color}"/>
</Style>
<DataTemplate x:Key="Template_PJZS_SHANGJIASHIBAI">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="80"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding AppCode,Converter={StaticResource appCodeCtr}}"
VerticalAlignment="Center" Margin="10,0,0,0" FontSize="14"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:BAsyncImage UrlSource="{Binding SpuLogo}" DecodePixelWidth="70" Width="70"
Grid.RowSpan="2"
VerticalAlignment="Top"/>
<TextBlock Grid.Column="1">
<Run Text="活动名称:" Foreground="{StaticResource Text.Property.Color}"/>
<Run Text="{Binding ActivityName}"/>
</TextBlock>
<TextBlock Grid.Column="1" Grid.Row="1" VerticalAlignment="Top">
<Run Text="SPU:" Foreground="{StaticResource Text.Property.Color}"/>
<Run Text="{Binding MainProductSpu}"/>
</TextBlock>
</Grid>
<TextBlock Text="{Binding ErrorMsg}"
TextWrapping="Wrap"
Grid.Row="2"
Margin="10,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="Template_Normal">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding AppCode,Converter={StaticResource appCodeCtr}}"
VerticalAlignment="Center" Margin="10,0,0,0" FontSize="14"/>
<TextBlock Text="{Binding Content}" Grid.Row="1" Margin="10,0" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
<templateSelector:MessageTemplateSelector x:Key="mtSelector"
Template_Normal="{StaticResource Template_Normal}"
Template_PJZS_SHANGJIASHIBAI="{StaticResource Template_PJZS_SHANGJIASHIBAI}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="35"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Border BorderBrush="{StaticResource Border.Brush}"
Background="{StaticResource Border.Background}"/>
<TextBlock Text="{Binding Msg.Title}" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="10,0,0,0"
Foreground="White" FontSize="14"/>
<c:BButton HorizontalAlignment="Right" Margin="0,0,10,0" BorderThickness="0"
x:Name="btn_Close" Click="btn_Close_Click">
<Path x:Name="p" Stretch="Uniform" SnapsToDevicePixels="True" UseLayoutRounding="True"
Data="M814.060 781.227q-67.241-67.241-269.773-269.773 67.241-67.241 269.773-269.773 5.671-6.481 5.671-12.962 0 0-0.81-0.81 0-6.481-4.861-9.722-4.861-4.051-11.342-4.861-0.81 0-0.81 0-5.671 0-11.342 4.861-89.924 89.924-269.773 269.773-67.241-67.241-269.773-269.773-4.861-4.861-12.962-4.861-7.291 0.81-10.532 4.861-5.671 5.671-5.671 11.342 0 6.481 5.671 12.152 89.924 89.924 269.773 269.773-67.241 67.241-269.773 269.773-11.342 11.342 0 23.494 12.152 11.342 23.494 0 89.924-89.924 269.773-269.773 67.241 67.241 269.773 269.773 5.671 5.671 11.342 5.671 5.671 0 12.152-5.671 4.861-5.671 4.861-12.962 0-6.481-4.861-10.532z"
Width="14" Height="14" Stroke="White"/>
</c:BButton>
<c:BButton Style="{StaticResource LinkButton}"
Content="查看详情"
Grid.Row="2"
Margin="10,0,0,0"
HorizontalAlignment="Left"/>
<c:BButton x:Name="btn_showNext" Style="{StaticResource LinkButton}"
Content="下一条"
Grid.Row="2"
Margin="0,0,10,0"
HorizontalAlignment="Right"
Click="btn_showNext_Click"/>
<ContentControl Content="{Binding Msg,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ContentTemplateSelector="{StaticResource mtSelector}"
Grid.Row="1"/>
</Grid>
</c:BWindow>

47
QYMessageCenter.Client/PopupWindow.xaml.cs

@ -0,0 +1,47 @@
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Extensions.DependencyInjection;
using QYMessageCenter.Client.Models;
using SJ.Controls;
using System.Windows;
namespace QYMessageCenter.Client
{
/// <summary>
/// PopupWindow.xaml 的交互逻辑
/// </summary>
public partial class PopupWindow : BWindow
{
private Message msg;
public Message Msg { get => msg; set { Set(ref msg, value); } }
public PopupWindow(Message msg)
{
InitializeComponent();
this.Msg = msg;
this.DataContext = this;
this.Loaded += PopupWindow_Loaded;
}
private void PopupWindow_Loaded(object sender, RoutedEventArgs e)
{
//弹窗显示在右下角
this.Left = SystemParameters.WorkArea.Width - this.Width;
this.Top = SystemParameters.WorkArea.Height - this.Height;
}
public void RefreshMsg(Message msg)
{
this.Msg = msg;
}
private void btn_showNext_Click(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send(new MVVMMessage_ShowNext(null));
}
private void btn_Close_Click(object sender, RoutedEventArgs e)
{
WeakReferenceMessenger.Default.Send(new MVVMMessage_ShowNext(null));
}
}
}

2
QYMessageCenter.Client/QYMessageCenter.Client.csproj

@ -19,11 +19,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2277.86" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QYMessageCenter.Common\QYMessageCenter.Common.csproj" />
<ProjectReference Include="..\SJ.Controls\SJ.Controls.csproj" />
</ItemGroup>
</Project>

19
QYMessageCenter.Client/Resources/Themes/Generic.xaml

@ -0,0 +1,19 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:SJ.Controls;assembly=SJ.Controls">
<SolidColorBrush x:Key="WindowButtonColor" Color="Black"/>
<SolidColorBrush x:Key="Text.Link.Color" Color="#02A7F0"/>
<Style x:Key="LinkButton" TargetType="{x:Type c:BButton}">
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{StaticResource Text.Link.Color}"/>
</Style>
<Style x:Key="bwstyle" TargetType="c:BWindow">
<Setter Property="MaxButtonColor" Value="{StaticResource WindowButtonColor}"/>
<Setter Property="MinButtonColor" Value="{StaticResource WindowButtonColor}"/>
<Setter Property="CloseButtonColor" Value="{StaticResource WindowButtonColor}"/>
<Setter Property="RightButtonGroupMargin" Value="0,5,5,0"/>
</Style>
</ResourceDictionary>

30
QYMessageCenter.Client/TemplateSelector/MessageTemplateSelector.cs

@ -0,0 +1,30 @@
using QYMessageCenter.Client.Models;
using System.Windows;
using System.Windows.Controls;
namespace QYMessageCenter.Client.TemplateSelector
{
public class MessageTemplateSelector : DataTemplateSelector
{
public DataTemplate Template_Normal { get; set; }
public DataTemplate Template_PJZS_SHANGJIASHIBAI { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return null;
var msg = item as Message;
if (msg.AppCode == "PJZS")
{
if (msg.CustomTypeCode == "SHANGJIASHIBAI")
{
return Template_PJZS_SHANGJIASHIBAI;
}
}
return Template_Normal;
}
}
}

99
QYMessageCenter.Client/static/index.html

@ -4,52 +4,71 @@
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://cdn.goeasy.io/goeasy-2.11.1.min.js"></script>
</head>
<body>
<script>
var hostOjbect = window.chrome.webview.hostObjects.sync.qymsgcenter;
<script type="text/javascript" src="https://cdn.goeasy.io/goeasy-2.11.1.min.js"></script>
<script type="text/javascript" async>
var user;
var goeasy;
async function init() {
var hostOjbect = await window?.chrome?.webview?.hostObjects?.sync?.qymsgcenter;
var userString = hostOjbect.GetUserString();
user = JSON.parse(userString);
//alert(user.Name);
goeasy = GoEasy.getInstance({
host: "hangzhou.goeasy.io", //若是新加坡区域:singapore.goeasy.io
appkey: "BC-652c4236c6ba4083b026e8cfa2e199b1",
modules: ['pubsub']//根据需要,传入‘pubsub’或'im’,或数组方式同时传入
});
//建立连接
goeasy.connect({
onSuccess: function () { //连接成功
//hostOjbect.OnError("GoEasy connect successfully");
console.log("GoEasy connect successfully.") //连接成功
subscribe();
},
onFailed: function (error) { //连接失败
var msg = "Failed to connect GoEasy, code:" + error.code + ",error:" + error.content;
hostOjbect.OnError(msg);
console.log(msg);
},
onProgress: function (attempts) { //连接或自动重连中
console.log("GoEasy is connecting", attempts);
}
});
}
function subscribe() {
var pubsub = goeasy.pubsub;
for (var i = 0; i < user.DepartmentList.length; i++) {
var channel = user.DepartmentList[i].Id;
pubsub.subscribe({
channel: channel,//替换为您自己的channel
onMessage: function (message) {
//收到消息
console.log("Channel:" + message.channel + " content:" + message.content);
hostOjbect.OnReceiveMessage(message.channel, message.content);
},
onSuccess: function () {
//hostOjbect.OnError("Channel订阅成功。");
var msg = `channel ${channel} 订阅成功`;
console.log(msg);
},
onFailed: function (error) {
var msg = "Channel订阅失败, 错误编码:" + error.code + " 错误信息:" + error.content;
hostOjbect.OnError(msg);
console.log(msg)
}
});
}
}
//let goeasy = GoEasy.getInstance({
// host: "hangzhou.goeasy.io", //若是新加坡区域:singapore.goeasy.io
// appkey: "BC-652c4236c6ba4083b026e8cfa2e199b1",
// modules: ['pubsub']//根据需要,传入‘pubsub’或'im’,或数组方式同时传入
//});
////建立连接
//goeasy.connect({
// onSuccess: function () { //连接成功
// console.log("GoEasy connect successfully.") //连接成功
// subscribe();
// },
// onFailed: function (error) { //连接失败
// console.log("Failed to connect GoEasy, code:" + error.code + ",error:" + error.content);
// },
// onProgress: function (attempts) { //连接或自动重连中
// console.log("GoEasy is connecting", attempts);
// }
//});
//function subscribe() {
// var pubsub = goeasy.pubsub;
// pubsub.subscribe({
// channel: "my_channel",//替换为您自己的channel
// onMessage: function (message) {
// //收到消息
// console.log("Channel:" + message.channel + " content:" + message.content);
// },
// onSuccess: function () {
// console.log("Channel订阅成功。");
// },
// onFailed: function (error) {
// console.log("Channel订阅失败, 错误编码:" + error.code + " 错误信息:" + error.content)
// }
// });
//}
init();
</script>
</body>
</html>

8
QYMessageCenter.Model/DB/QYNotification.cs

@ -4,7 +4,7 @@ namespace QYMessageCenter.Model.DB
{
[Table(Name = "qynotification", DisableSyncStructure = true)]
public partial class Qynotification
public partial class QYNotification
{
[Column(DbType = "bigint", IsPrimary = true)]
@ -34,6 +34,12 @@ namespace QYMessageCenter.Model.DB
[Column(StringLength = 50)]
public string ShopId { get; set; }
/// <summary>
/// 标题
/// </summary>
[Column(StringLength = 100)]
public string Title { get; set; }
/// <summary>
/// 消息内容
/// </summary>

5
QYMessageCenter.Model/DTO/Message/SendMessageRequest.cs

@ -27,6 +27,11 @@ namespace QYMessageCenter.Model.DTO
/// </summary>
public string ShopId { get; set; }
/// <summary>
/// 消息标题
/// </summary>
public string Title { get; set; }
/// <summary>
/// 消息内容
/// </summary>

9
QYMessageCenter.sln

@ -15,7 +15,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Server", "Server", "{F23D88
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Client", "Client", "{DFE55D4D-6A43-4158-AB78-7B36D0DB351B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QYMessageCenter.Client", "QYMessageCenter.Client\QYMessageCenter.Client.csproj", "{D5998DAB-6517-42EB-A604-BF88C189F1B4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QYMessageCenter.Client", "QYMessageCenter.Client\QYMessageCenter.Client.csproj", "{D5998DAB-6517-42EB-A604-BF88C189F1B4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SJ.Controls", "SJ.Controls\SJ.Controls.csproj", "{D05F067C-90F5-42EF-808B-47FCA5B5E4FE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -43,6 +45,10 @@ Global
{D5998DAB-6517-42EB-A604-BF88C189F1B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D5998DAB-6517-42EB-A604-BF88C189F1B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D5998DAB-6517-42EB-A604-BF88C189F1B4}.Release|Any CPU.Build.0 = Release|Any CPU
{D05F067C-90F5-42EF-808B-47FCA5B5E4FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D05F067C-90F5-42EF-808B-47FCA5B5E4FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D05F067C-90F5-42EF-808B-47FCA5B5E4FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D05F067C-90F5-42EF-808B-47FCA5B5E4FE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -52,6 +58,7 @@ Global
{17CB9C4E-4008-4E5A-98B2-40B666922CE1} = {F23D8876-9ED2-4EC1-9856-0E6AC3E58072}
{91F9B6F7-EB22-42AE-80A9-0FF427B055A6} = {F23D8876-9ED2-4EC1-9856-0E6AC3E58072}
{D5998DAB-6517-42EB-A604-BF88C189F1B4} = {DFE55D4D-6A43-4158-AB78-7B36D0DB351B}
{D05F067C-90F5-42EF-808B-47FCA5B5E4FE} = {DFE55D4D-6A43-4158-AB78-7B36D0DB351B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F8628884-5171-4D3A-9A40-9C788CBA7EEB}

5
QYMessageCenter/SWDescription.txt

@ -15,5 +15,6 @@ SN 司南
Channel规范
CustomTypeCode说明
评价助手:
需要特殊处理的CustomTypeCode说明
评价助手
SHANGJIASHIBAI 上架失败

10
SJ.Controls/AssemblyInfo.cs

@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

705
SJ.Controls/BAsyncImage.cs

@ -0,0 +1,705 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using dw = System.Drawing;
namespace SJ.Controls
{
public class BAsyncImage : Control
{
#region DependencyProperty
public static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register("DecodePixelWidth",
typeof(double), typeof(BAsyncImage), new PropertyMetadata(0.0));
public static readonly DependencyProperty LoadingTextProperty =
DependencyProperty.Register("LoadingText", typeof(string), typeof(BAsyncImage), new PropertyMetadata("Loading..."));
public static readonly DependencyProperty IsLoadingProperty =
DependencyProperty.Register("IsLoading", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(false));
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(BAsyncImage));
public static readonly DependencyProperty DefaultUrlSourceProperty = DependencyProperty.Register("DefaultUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(""));
public static readonly DependencyProperty UrlSourceProperty =
DependencyProperty.Register("UrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback((s, e) =>
{
var asyncImg = s as BAsyncImage;
if (asyncImg.LoadEventFlag)
{
asyncImg.Load();
}
})));
public static readonly DependencyProperty FailUrlSourceProperty =
DependencyProperty.Register("FailUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty));
public static readonly DependencyProperty IsCacheProperty = DependencyProperty.Register("IsCache", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(true));
public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(BAsyncImage), new PropertyMetadata(Stretch.Uniform));
public static readonly DependencyProperty CacheGroupProperty = DependencyProperty.Register("CacheGroup", typeof(string), typeof(BAsyncImage), new PropertyMetadata("QLAsyncImage_Default"));
#endregion
#region Property
private static readonly HttpClient httpClient = new HttpClient();
public double StaticImageActualPixelWidth { get; set; } = 0;
public double StaticImageActualPixelHeight { get; set; } = 0;
public const string LocalRegex = @"^([C-J]):\\([^:&]+\\)*([^:&]+).(jpg|jpeg|png|gif)$";
public const string HttpRegex = @"^((https|http):\/\/)?([^\\*+@]+)$";
private Image _image;
private dw.Bitmap gifBitmap;
private bool LoadEventFlag = false;
private static ConcurrentDictionary<string, ImageSource> ImageCacheList;
private static ConcurrentDictionary<string, byte[]> GifImageCacheList;
private static LimitedConcurrencyLevelTaskScheduler httpTaskScheduler;
public double DecodePixelWidth
{
get { return (double)GetValue(DecodePixelWidthProperty); }
set { SetValue(DecodePixelWidthProperty, value); }
}
public string LoadingText
{
get { return GetValue(LoadingTextProperty) as string; }
set { SetValue(LoadingTextProperty, value); }
}
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public string UrlSource
{
get { return GetValue(UrlSourceProperty) as string; }
set { SetValue(UrlSourceProperty, value); }
}
/// <summary>
/// 仅限Resourcesl路径
/// </summary>
public string FailUrlSource
{
get { return GetValue(FailUrlSourceProperty) as string; }
set { SetValue(FailUrlSourceProperty, value); }
}
public ImageSource ImageSource
{
get { return GetValue(ImageSourceProperty) as ImageSource; }
set { SetValue(ImageSourceProperty, value); }
}
public bool IsCache
{
get { return (bool)GetValue(IsCacheProperty); }
set { SetValue(IsCacheProperty, value); }
}
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
public string CacheGroup
{
get { return GetValue(CacheGroupProperty) as string; }
set { SetValue(CacheGroupProperty, value); }
}
#endregion
#region RouteEvent
public delegate void QLAsyncImageLoadCompleteHandler(object sender, QLAsyncImageLoadCompleteEventArgs e);
public static readonly RoutedEvent OnLoadCompleteEvent = EventManager.RegisterRoutedEvent("OnLoadComplete", RoutingStrategy.Bubble, typeof(QLAsyncImageLoadCompleteHandler), typeof(BAsyncImage));
public event QLAsyncImageLoadCompleteHandler OnLoadComplete
{
add { AddHandler(OnLoadCompleteEvent, value); }
remove { RemoveHandler(OnLoadCompleteEvent, value); }
}
#endregion
#region Method
static BAsyncImage()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BAsyncImage), new FrameworkPropertyMetadata(typeof(BAsyncImage)));
ImageCacheList = new ConcurrentDictionary<string, ImageSource>();
GifImageCacheList = new ConcurrentDictionary<string, byte[]>();
httpTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
}
public BAsyncImage()
{
Loaded += QLAsyncImage_Loaded;
Unloaded += QLAsyncImage_Unloaded;
}
private void QLAsyncImage_Unloaded(object sender, RoutedEventArgs e)
{
Reset();
LoadEventFlag = false;
}
private void QLAsyncImage_Loaded(object sender, RoutedEventArgs e)
{
Init();
}
public override void OnApplyTemplate()
{
Init();
}
private void Init([CallerMemberName] string eventName = "")
{
if (LoadEventFlag)
return;
_image = GetTemplateChild("image") as Image;
if (_image == null)
return;
LoadEventFlag = true;
Load();
}
/// <summary>
/// Delete local bitmap resource
/// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
/// </summary>
[DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool DeleteObject(IntPtr hObject);
/// <summary>
/// 重置图像
/// </summary>
private void Reset()
{
//停止播放Gif动画
if (gifBitmap != null)
StopGif();
SetSource(null);
}
private void Load()
{
if (_image == null)
return;
Reset();
if (!string.IsNullOrEmpty(UrlSource))
{
var url = UrlSource;
var failUrl = FailUrlSource;
var pixelWidth = (int)DecodePixelWidth;
var isCache = IsCache;
var cacheKey = string.Format("{0}_{1}", CacheGroup, url);
IsLoading = !ImageCacheList.ContainsKey(cacheKey) && !GifImageCacheList.ContainsKey(cacheKey);
#region 读取缓存
if (ImageCacheList.ContainsKey(cacheKey))
{
var source = ImageCacheList[cacheKey];
SetSource(source);
SetStaticImageActualPixelSize(source.Width, source.Height);
LoadComplete(string.Empty, url);
return;
}
else if (GifImageCacheList.ContainsKey(cacheKey))
{
PlayGif(GifImageCacheList[cacheKey]);
LoadComplete(string.Empty, url);
return;
}
#endregion
this.Load(url, failUrl, cacheKey, isCache, pixelWidth);
}
}
private void Load(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
{
var errorMessage = string.Empty;
//解析路径类型
var pathType = ValidatePathType(url);
if (pathType == PathType.Invalid)
{
LoadFail(failUrl);
LoadComplete(errorMessage, url);
return;
}
if (pathType == PathType.Http)
{
////先加载默认图
//if (!string.IsNullOrEmpty(defaultUrl))
// LoadLocal(defaultUrl, failUrl, PathType.Resources, defaultUrl, true, 0, excuteComplete: false); //默认图不触发加载完毕事件
LoadHttp(url, failUrl, cacheKey, isCache, pixelWidth);
}
else
LoadLocal(url, failUrl, pathType, cacheKey, isCache, pixelWidth);
}
/// <summary>
/// 加载失败图像
/// </summary>
/// <param name="failUrl"></param>
private void LoadFail(string failUrl)
{
byte[] imgBytes = null;
string errorMessage = string.Empty;
var pathType = ValidatePathType(failUrl);
if (pathType == PathType.Invalid)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"LoadFail 无效的路径 {failUrl}");
Console.ResetColor();
return;
}
if (pathType == PathType.Local)
imgBytes = LoadBytesFromLocal(failUrl, out errorMessage);
else if (pathType == PathType.Resources)
imgBytes = LoadBytesFromApplicationResource(failUrl, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, failUrl, true, 0, out errorMessage);
if (string.IsNullOrEmpty(errorMessage))
return;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"LoadFail {errorMessage} {failUrl}");
Console.ResetColor();
}
private void LoadLocal(string url, string failUrl, PathType pathType, string cacheKey, bool isCache, int pixelWidth, bool excuteComplete = true)
{
byte[] imgBytes = null;
var errorMessage = string.Empty;
if (pathType == PathType.Local)
imgBytes = LoadBytesFromLocal(url, out errorMessage);
else if (pathType == PathType.Resources)
imgBytes = LoadBytesFromApplicationResource(url, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);
if (!string.IsNullOrEmpty(errorMessage))
{
LoadFail(failUrl);
Console.ForegroundColor = ConsoleColor.Red;
}
Console.WriteLine($"LoadLocal {errorMessage} {url}");
Console.ResetColor();
if (excuteComplete)
LoadComplete(errorMessage, url);
return;
}
private void LoadHttp(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
{
Task.Factory.StartNew(() =>
{
//Thread.Sleep(2000);
Console.WriteLine($"LoadHttp Start {url}");
var errorMessage = string.Empty;
var imgBytes = LoadBytesFromHttp(url, out errorMessage);
if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);
if (!string.IsNullOrEmpty(errorMessage))
{
LoadFail(failUrl);
Console.ForegroundColor = ConsoleColor.Red;
}
Console.WriteLine($"LoadHttp Completed {errorMessage} {url}");
Console.ResetColor();
LoadComplete(errorMessage, url);
return;
}, CancellationToken.None, TaskCreationOptions.None, httpTaskScheduler);
}
private void AnalysisBytes(byte[] imgBytes, string cacheKey, bool isCache, int pixelWidth, out string errorMessage)
{
errorMessage = string.Empty;
#region 读取文件类型
var imgType = GetImageType(imgBytes);
if (imgType == ImageType.Invalid)
{
imgBytes = null;
errorMessage = "Invalid ImageFile";
return;
}
#endregion
#region 加载图像
if (imgType != ImageType.Gif)
{
//加载静态图像
var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
if (imgSource == null)
return;
SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
SetSource(imgSource);
}
else
{
var frameCount = 0;
using (var memoryStream = new System.IO.MemoryStream(imgBytes))
{
//读取gif帧数
var decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
frameCount = decoder.Frames.Count;
decoder = null;
}
if (frameCount > 1)
{
CacheGifBytes(cacheKey, imgBytes, isCache);
PlayGif(imgBytes);
}
else
{
//gif只有1帧,视为静态图处理
var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
if (imgSource == null)
return;
SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
SetSource(imgSource);
}
}
#endregion
}
/// <summary>
/// 加载静态图像
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
private ImageSource LoadStaticImage(string cacheKey, byte[] imgBytes, int pixelWidth, bool isCache, out string errorMessage)
{
errorMessage = string.Empty;
if (ImageCacheList.ContainsKey(cacheKey))
return ImageCacheList[cacheKey];
var bit = new BitmapImage() { CacheOption = BitmapCacheOption.OnLoad };
try
{
bit.BeginInit();
if (pixelWidth != 0)
{
bit.DecodePixelWidth = pixelWidth;
}
bit.StreamSource = new System.IO.MemoryStream(imgBytes);
bit.EndInit();
bit.Freeze();
if (isCache && !ImageCacheList.ContainsKey(cacheKey))
ImageCacheList.TryAdd(cacheKey, bit);
}
catch (Exception ex)
{
errorMessage = $"LoadStaticImage Error {ex.Message}";
bit = null;
}
return bit;
}
/// <summary>
/// 加载Gif图像动画
/// </summary>
/// <param name="cacheKey"></param>
/// <param name="imgBytes"></param>
/// <param name="pixelWidth"></param>
/// <param name="isCache"></param>
/// <returns></returns>
private void CacheGifBytes(string cacheKey, byte[] imgBytes, bool isCache)
{
if (isCache && !GifImageCacheList.ContainsKey(cacheKey))
GifImageCacheList.TryAdd(cacheKey, imgBytes);
}
private byte[] LoadBytesFromHttp(string url, out string errorMessage)
{
errorMessage = string.Empty;
try
{
return httpClient.GetByteArrayAsync(url).Result;
}
catch (Exception ex)
{
errorMessage = $"Dowdload Error {ex.Message}";
}
return null;
}
private byte[] LoadBytesFromLocal(string path, out string errorMessage)
{
errorMessage = string.Empty;
if (!System.IO.File.Exists(path))
{
errorMessage = "File No Exists";
return null;
}
try
{
return System.IO.File.ReadAllBytes(path);
}
catch (Exception ex)
{
errorMessage = $"Load Local Error {ex.Message}";
return null;
}
}
private byte[] LoadBytesFromApplicationResource(string path, out string errorMessage)
{
errorMessage = string.Empty;
try
{
StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(path, UriKind.RelativeOrAbsolute));
if (streamInfo.Stream.CanRead)
{
using (streamInfo.Stream)
{
var bytes = new byte[streamInfo.Stream.Length];
streamInfo.Stream.Read(bytes, 0, bytes.Length);
return bytes;
}
}
}
catch (Exception ex)
{
errorMessage = $"Load Resource Error {ex.Message}";
return null;
}
return null;
}
private void SetSource(ImageSource source)
{
Dispatcher.BeginInvoke((Action)delegate
{
if (_image != null)
_image.Source = source;
});
}
/// <summary>
/// 更新图像实际像素
/// </summary>
/// <param name="pixelWidth"></param>
private void SetStaticImageActualPixelSize(double pixelWidth, double pixelHeight)
{
Dispatcher.Invoke(() =>
{
StaticImageActualPixelWidth = pixelWidth;
StaticImageActualPixelHeight = pixelHeight;
});
}
private void PlayGif(byte[] imgBytes)
{
gifBitmap = new dw.Bitmap(new System.IO.MemoryStream(imgBytes));
if (dw.ImageAnimator.CanAnimate(gifBitmap))
{
SetStaticImageActualPixelSize(gifBitmap.Width, gifBitmap.Height);
dw.ImageAnimator.Animate(gifBitmap, OnGifFrameChanged);
}
else
{
gifBitmap.Dispose();
}
}
private void StopGif()
{
dw.ImageAnimator.StopAnimate(gifBitmap, OnGifFrameChanged);
gifBitmap.Dispose();
}
private void OnGifFrameChanged(object sender, EventArgs e)
{
dw.ImageAnimator.UpdateFrames();
var currentFrameImageSource = GetBitmapSource();
if (currentFrameImageSource != null)
currentFrameImageSource.Freeze();
SetSource(currentFrameImageSource);
}
/// <summary>
/// 加载完成
/// </summary>
/// <param name="errorMessage"></param>
/// <param name="url"></param>
private void LoadComplete(string errorMessage, string url)
{
this.Dispatcher.BeginInvoke((Action)delegate
{
IsLoading = false;
var args = new QLAsyncImageLoadCompleteEventArgs(OnLoadCompleteEvent, this)
{
IsSuccess = string.IsNullOrEmpty(errorMessage),
UrlSource = url,
ErrorMessage = errorMessage,
StaticImageActualPixelWidth = StaticImageActualPixelWidth,
StaticImageActualPixelHeight = StaticImageActualPixelHeight
};
this.RaiseEvent(args);
});
}
/// <summary>
/// 从System.Drawing.Bitmap中获得当前帧图像的BitmapSource
/// </summary>
/// <returns></returns>
private ImageSource GetBitmapSource()
{
IntPtr handle = IntPtr.Zero;
try
{
handle = gifBitmap.GetHbitmap();
return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
}
catch
{
return null;
}
finally
{
if (handle != IntPtr.Zero)
{
DeleteObject(handle);
}
}
}
public void Refresh()
{
Load();
}
private PathType ValidatePathType(string path)
{
if (path.StartsWith("pack://"))
return PathType.Resources;
else if (Regex.IsMatch(path, BAsyncImage.LocalRegex, RegexOptions.IgnoreCase))
return PathType.Local;
else if (Regex.IsMatch(path, BAsyncImage.HttpRegex, RegexOptions.IgnoreCase))
return PathType.Http;
else
return PathType.Invalid;
}
private ImageType GetImageType(byte[] bytes)
{
var type = ImageType.Invalid;
try
{
var fileHead = Convert.ToInt32($"{bytes[0]}{bytes[1]}");
if (!Enum.IsDefined(typeof(ImageType), fileHead))
{
type = ImageType.Invalid;
}
else
{
type = (ImageType)fileHead;
}
}
catch (Exception ex)
{
type = ImageType.Invalid;
Console.WriteLine($"获取图片类型失败 {ex.Message}");
}
return type;
}
/// <summary>
/// 清楚缓存
/// </summary>
/// <param name="cacheKey">缓存Key,格式: CacheGroup_UrlSource</param>
public static void ClearCache(string cacheKey = "")
{
if (string.IsNullOrEmpty(cacheKey))
{
ImageCacheList.Clear();
GifImageCacheList.Clear();
return;
}
ImageCacheList.Remove(cacheKey, out _);
GifImageCacheList.Remove(cacheKey, out _);
}
public static ImageSource GetImageCache(string cacheKey)
{
if (ImageCacheList.ContainsKey(cacheKey))
return ImageCacheList[cacheKey];
return null;
}
#endregion
}
public enum PathType
{
Invalid = 0, Local = 1, Http = 2, Resources = 3
}
public enum ImageType
{
Invalid = 0, Gif = 7173, Jpg = 255216, Png = 13780, Bmp = 6677
}
public class QLAsyncImageLoadCompleteEventArgs : RoutedEventArgs
{
public QLAsyncImageLoadCompleteEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }
public bool IsSuccess { get; set; }
public string UrlSource { get; set; }
public string ErrorMessage { get; set; }
/// <summary>
/// 当加载静态图时的实际像素宽度
/// </summary>
public double StaticImageActualPixelWidth { get; set; }
/// <summary>
/// 当加载静态图时的实际像素高度
/// </summary>
public double StaticImageActualPixelHeight { get; set; }
}
}

72
SJ.Controls/BButton.cs

@ -0,0 +1,72 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SJ.Controls
{
[StyleTypedProperty(Property = "Style", StyleTargetType = typeof(BButton))]
public class BButton : Button
{
static BButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BButton), new FrameworkPropertyMetadata(typeof(BButton)));
}
public static readonly DependencyProperty BorderCornerRadiusProperty = DependencyProperty.Register("BorderCornerRadius", typeof(CornerRadius), typeof(BButton));
public static readonly DependencyProperty MouseOverBgColorProperty = DependencyProperty.Register("MouseOverBgColor", typeof(Brush), typeof(BButton));
public static readonly DependencyProperty MouseOverFontColorProperty = DependencyProperty.Register("MouseOverFontColor", typeof(Brush), typeof(BButton));
public static readonly DependencyProperty PressedBgColorProperty = DependencyProperty.Register("PressedBgColor", typeof(Brush), typeof(BButton));
public static readonly DependencyProperty PressedFontColorProperty = DependencyProperty.Register("PressedFontColor", typeof(Brush), typeof(BButton));
public static readonly DependencyProperty DisableBgColorProperty = DependencyProperty.Register("DisableBgColor", typeof(Brush), typeof(BButton));
public static readonly DependencyProperty DisableTextProperty = DependencyProperty.Register("DisableText", typeof(string), typeof(BButton));
public static readonly DependencyProperty PressedScaleProperty = DependencyProperty.Register("PressedScale", typeof(bool), typeof(BButton), new PropertyMetadata(true));
public CornerRadius BorderCornerRadius
{
get { return (CornerRadius)GetValue(BorderCornerRadiusProperty); }
set { SetValue(BorderCornerRadiusProperty, value); }
}
public Brush MouseOverBgColor
{
get { return GetValue(MouseOverBgColorProperty) as Brush; }
set { SetValue(MouseOverBgColorProperty, value); }
}
public Brush MouseOverFontColor
{
get { return GetValue(MouseOverFontColorProperty) as Brush; }
set { SetValue(MouseOverFontColorProperty, value); }
}
public Brush PressedBgColor
{
get { return GetValue(PressedBgColorProperty) as Brush; }
set { SetValue(PressedBgColorProperty, value); }
}
public Brush PressedFontColor
{
get { return GetValue(PressedFontColorProperty) as Brush; }
set { SetValue(PressedFontColorProperty, value); }
}
public Brush DisableBgColor
{
get { return GetValue(DisableBgColorProperty) as System.Windows.Media.Brush; }
set { SetValue(DisableBgColorProperty, value); }
}
public string DisableText
{
get { return GetValue(DisableTextProperty).ToString(); }
set { SetValue(DisableTextProperty, value); }
}
public bool PressedScale
{
get { return (bool)GetValue(PressedScaleProperty); }
set { SetValue(PressedScaleProperty, value); }
}
}
}

210
SJ.Controls/BTextBox.cs

@ -0,0 +1,210 @@
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace SJ.Controls
{
[StyleTypedProperty(Property = "Style", StyleTargetType = typeof(BTextBox))]
public class BTextBox : TextBox
{
#region Property
private bool IsResponseChange;
private StringBuilder PasswordBuilder;
private int lastOffset;
#endregion
#region DependencyProperty
public static readonly DependencyProperty WaterRemarkProperty = DependencyProperty.Register("WaterRemark", typeof(string), typeof(BTextBox));
public static readonly DependencyProperty WaterRemarkFontColorProperty = DependencyProperty.Register("WaterRemarkFontColor", typeof(Brush), typeof(BTextBox));
public static readonly DependencyProperty BorderCornerRadiusProperty = DependencyProperty.Register("BorderCornerRadius", typeof(CornerRadius), typeof(BTextBox));
public static readonly DependencyProperty IsPasswordBoxProperty = DependencyProperty.Register("IsPasswordBox", typeof(bool), typeof(BTextBox), new FrameworkPropertyMetadata(false, new PropertyChangedCallback(OnIsPasswordBoxChnage)));
public static readonly DependencyProperty IsNumberBoxProperty = DependencyProperty.Register("IsNumberBox", typeof(bool), typeof(BTextBox), new PropertyMetadata(false, new PropertyChangedCallback(OnIsNumberBoxChnage)));
public static readonly DependencyProperty DisableBgColorProperty = DependencyProperty.Register("DisableBgColor", typeof(Brush), typeof(BTextBox));
public static readonly DependencyProperty PasswordCharProperty = DependencyProperty.Register("PasswordChar", typeof(char), typeof(BTextBox), new FrameworkPropertyMetadata('●'));
public static readonly DependencyProperty PasswordStrProperty = DependencyProperty.Register("PasswordStr", typeof(string), typeof(BTextBox), new FrameworkPropertyMetadata(string.Empty, new PropertyChangedCallback(OnPasswordStrChanged)));
/// <summary>
/// 水印文字
/// </summary>
public string WaterRemark
{
get { return GetValue(WaterRemarkProperty).ToString(); }
set { SetValue(WaterRemarkProperty, value); }
}
public Brush WaterRemarkFontColor
{
get { return GetValue(WaterRemarkFontColorProperty) as Brush; }
set { SetValue(WaterRemarkFontColorProperty, value); }
}
/// <summary>
/// 边框角度
/// </summary>
public CornerRadius BorderCornerRadius
{
get { return (CornerRadius)GetValue(BorderCornerRadiusProperty); }
set { SetValue(BorderCornerRadiusProperty, value); }
}
/// <summary>
/// 是否为密码框
/// </summary>
public bool IsPasswordBox
{
get { return (bool)GetValue(IsPasswordBoxProperty); }
set { SetValue(IsPasswordBoxProperty, value); }
}
/// <summary>
/// 是否为数字框
/// </summary>
public bool IsNumberBox
{
get { return (bool)GetValue(IsNumberBoxProperty); }
set { SetValue(IsNumberBoxProperty, value); }
}
/// <summary>
/// 替换明文的密码字符
/// </summary>
public char PasswordChar
{
get { return (char)GetValue(PasswordCharProperty); }
set { SetValue(PasswordCharProperty, value); }
}
/// <summary>
/// 密码字符串
/// </summary>
public string PasswordStr
{
get
{
var value = GetValue(PasswordStrProperty);
return value == null ? string.Empty : value.ToString();
}
set { SetValue(PasswordStrProperty, value); }
}
/// <summary>
/// 按钮被禁用时的背景颜色
/// </summary>
public Brush DisableBgColor
{
get { return GetValue(DisableBgColorProperty) as Brush; }
set { SetValue(DisableBgColorProperty, value); }
}
#endregion
static BTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BTextBox), new FrameworkPropertyMetadata(typeof(BTextBox)));
}
public BTextBox()
{
IsResponseChange = true;
PasswordBuilder = new StringBuilder();
this.Loaded += QLTextBox_Loaded;
}
private void QLTextBox_Loaded(object sender, RoutedEventArgs e)
{
if (IsPasswordBox && !string.IsNullOrEmpty(PasswordStr) && PasswordStr.Length > 0)
{
OnPasswordStrChanged();
}
}
private static void OnPasswordStrChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
(d as BTextBox).OnPasswordStrChanged();
}
private void OnPasswordStrChanged()
{
if (!IsResponseChange)
return;
IsResponseChange = false;
this.Text = ConvertToPasswordChar(PasswordStr.Length);
IsResponseChange = true;
}
private static void OnIsPasswordBoxChnage(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as BTextBox).SetPwdEvent();
}
private static void OnIsNumberBoxChnage(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as BTextBox).SetValidationNumberEvent();
}
/// <summary>
/// 定义TextChange事件
/// </summary>
private void SetPwdEvent()
{
if (IsPasswordBox)
this.TextChanged += QLTextBox_TextChanged;
else
this.TextChanged -= QLTextBox_TextChanged;
}
private void SetValidationNumberEvent()
{
if (IsNumberBox)
this.PreviewTextInput += QLTextBox_PreviewTextInput;
else
this.PreviewTextInput -= QLTextBox_PreviewTextInput;
}
private void QLTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (!IsResponseChange)
return;
IsResponseChange = false;
foreach (TextChange c in e.Changes)
{
PasswordStr = PasswordStr.Remove(c.Offset, c.RemovedLength);
PasswordStr = PasswordStr.Insert(c.Offset, Text.Substring(c.Offset, c.AddedLength));
lastOffset = c.Offset;
}
/*将文本转换为密码字符*/
this.Text = ConvertToPasswordChar(Text.Length);
IsResponseChange = true;
this.SelectionStart = lastOffset + 1;
}
private void QLTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex re = new Regex("[^0-9.]+");
e.Handled = re.IsMatch(e.Text);
}
/// <summary>
/// 按照指定的长度生成密码字符
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
private string ConvertToPasswordChar(int length)
{
if (PasswordBuilder != null)
PasswordBuilder.Clear();
else
PasswordBuilder = new StringBuilder();
for (var i = 0; i < length; i++)
PasswordBuilder.Append(PasswordChar);
return PasswordBuilder.ToString();
}
}
}

109
SJ.Controls/BTextBoxAnimation.cs

@ -0,0 +1,109 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SJ.Controls
{
[StyleTypedProperty(Property = "Style", StyleTargetType = typeof(BTextBoxAnimation))]
public class BTextBoxAnimation : BTextBox
{
public static readonly DependencyProperty WaterRemarkTopStateColorProperty = DependencyProperty.Register("WaterRemarkTopStateColor", typeof(Brush), typeof(BTextBoxAnimation));
public static readonly DependencyProperty WaterRemarkStateProperty = DependencyProperty.Register("WaterRemarkState", typeof(WaterRemarkState), typeof(BTextBoxAnimation), new PropertyMetadata(WaterRemarkState.Normal, new PropertyChangedCallback((d, e) =>
{
if (e.OldValue != e.NewValue)
{
(d as BTextBoxAnimation).PlayWaterRemarkAnimation();
}
})));
private TextBlock txtRemark;
private TimeSpan animationTimeSpan = new TimeSpan(0, 0, 0, 0, 200);
private IEasingFunction animationEasingFunction = new PowerEase() { EasingMode = EasingMode.EaseInOut };
public Brush WaterRemarkTopStateColor
{
get { return GetValue(WaterRemarkTopStateColorProperty) as Brush; }
set { SetValue(WaterRemarkTopStateColorProperty, value); }
}
public WaterRemarkState WaterRemarkState
{
get { return (WaterRemarkState)Convert.ToInt32(GetValue(WaterRemarkStateProperty)); }
set { SetValue(WaterRemarkStateProperty, value); }
}
static BTextBoxAnimation()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BTextBoxAnimation), new FrameworkPropertyMetadata(typeof(BTextBoxAnimation)));
}
public BTextBoxAnimation()
{
this.Loaded += QLTextBoxAnimation_Loaded;
}
public override void OnApplyTemplate()
{
txtRemark = GetTemplateChild("txtRemark") as TextBlock;
base.OnApplyTemplate();
}
private void QLTextBoxAnimation_Loaded(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(Text))
WaterRemarkState = WaterRemarkState.Top;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (!this.IsLoaded)
return;
if (string.IsNullOrEmpty(Text))
WaterRemarkState = WaterRemarkState.Normal;
else
WaterRemarkState = WaterRemarkState.Top;
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
WaterRemarkState = WaterRemarkState.Top;
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if (string.IsNullOrEmpty(Text))
WaterRemarkState = WaterRemarkState.Normal;
}
private void PlayWaterRemarkAnimation()
{
var fontsize = WaterRemarkState == WaterRemarkState.Normal ? FontSize : 10.5;
var row = WaterRemarkState == WaterRemarkState.Normal ? 1 : 0;
var storyboard = new Storyboard();
var daukf_Remark_FontSize = new DoubleAnimationUsingKeyFrames();
daukf_Remark_FontSize.KeyFrames.Add(new EasingDoubleKeyFrame(fontsize, animationTimeSpan, animationEasingFunction));
Storyboard.SetTargetProperty(daukf_Remark_FontSize, new PropertyPath("(TextBlock.FontSize)"));
Storyboard.SetTarget(daukf_Remark_FontSize, txtRemark);
storyboard.Children.Add(daukf_Remark_FontSize);
var i32aukf_Remark_Row = new Int32AnimationUsingKeyFrames();
i32aukf_Remark_Row.KeyFrames.Add(new EasingInt32KeyFrame(row, animationTimeSpan, animationEasingFunction));
Storyboard.SetTargetProperty(i32aukf_Remark_Row, new PropertyPath("(Grid.Row)"));
Storyboard.SetTarget(i32aukf_Remark_Row, txtRemark);
storyboard.Children.Add(i32aukf_Remark_Row);
storyboard.Begin();
}
}
public enum WaterRemarkState
{
Normal, Top
}
}

178
SJ.Controls/BWindow.cs

@ -0,0 +1,178 @@
using System.ComponentModel;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shell;
namespace SJ.Controls
{
[StyleTypedProperty(Property = "Style", StyleTargetType = typeof(BWindow))]
public class BWindow : Window, INotifyPropertyChanged
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius), typeof(BWindow), new PropertyMetadata(new CornerRadius(0)));
public static readonly DependencyProperty RightButtonGroupMarginProperty =
DependencyProperty.Register("RightButtonGroupMargin", typeof(Thickness), typeof(BWindow), new PropertyMetadata(new Thickness(0, 16, 16, 0)));
public static readonly DependencyProperty CloseButtonVisibilityProperty =
DependencyProperty.Register("CloseButtonVisibility", typeof(Visibility), typeof(BWindow), new PropertyMetadata(Visibility.Visible));
public static readonly DependencyProperty MinButtonVisibilityProperty =
DependencyProperty.Register("MinButtonVisibility", typeof(Visibility), typeof(BWindow), new PropertyMetadata(Visibility.Visible));
public static readonly DependencyProperty MaxButtonVisibilityProperty =
DependencyProperty.Register("MaxButtonVisibility", typeof(Visibility), typeof(BWindow), new PropertyMetadata(Visibility.Visible));
public static readonly DependencyProperty CloseButtonColorProperty =
DependencyProperty.Register("CloseButtonColor", typeof(Brush), typeof(BWindow), new PropertyMetadata(new SolidColorBrush(Colors.White)));
public static readonly DependencyProperty MinButtonColorProperty =
DependencyProperty.Register("MinButtonColor", typeof(Brush), typeof(BWindow), new PropertyMetadata(new SolidColorBrush(Colors.White)));
public static readonly DependencyProperty MaxButtonColorProperty =
DependencyProperty.Register("MaxButtonColor", typeof(Brush), typeof(BWindow), new PropertyMetadata(new SolidColorBrush(Colors.White)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public Thickness RightButtonGroupMargin
{
get { return (Thickness)GetValue(RightButtonGroupMarginProperty); }
set { SetValue(RightButtonGroupMarginProperty, value); }
}
public Visibility CloseButtonVisibility
{
get { return (Visibility)GetValue(CloseButtonVisibilityProperty); }
set { SetValue(CloseButtonVisibilityProperty, value); }
}
public Visibility MinButtonVisibility
{
get { return (Visibility)GetValue(MinButtonVisibilityProperty); }
set { SetValue(MinButtonVisibilityProperty, value); }
}
public Visibility MaxButtonVisibility
{
get { return (Visibility)GetValue(MaxButtonVisibilityProperty); }
set { SetValue(MaxButtonVisibilityProperty, value); }
}
public Brush CloseButtonColor
{
get { return (Brush)GetValue(CloseButtonColorProperty); }
set { SetValue(CloseButtonColorProperty, value); }
}
public Brush MinButtonColor
{
get { return (Brush)GetValue(MinButtonColorProperty); }
set { SetValue(MinButtonColorProperty, value); }
}
public Brush MaxButtonColor
{
get { return (Brush)GetValue(MaxButtonColorProperty); }
set { SetValue(MaxButtonColorProperty, value); }
}
static BWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(BWindow), new FrameworkPropertyMetadata(typeof(BWindow)));
}
public BWindow()
{
WindowStartupLocation = WindowStartupLocation.CenterScreen;
var chrome = new WindowChrome
{
CornerRadius = new CornerRadius(),
GlassFrameThickness = new Thickness(1),
UseAeroCaptionButtons = false,
NonClientFrameEdges = NonClientFrameEdges.None,
ResizeBorderThickness = new Thickness(2),
CaptionHeight = 30
};
WindowChrome.SetWindowChrome(this, chrome);
}
public override void OnApplyTemplate()
{
Button PART_MIN = null;
Button PART_MAX = null;
Button PART_RESTORE = null;
Button PART_CLOSE = null;
PART_MIN = GetTemplateChild("PART_MIN") as Button;
PART_MAX = GetTemplateChild("PART_MAX") as Button;
PART_RESTORE = GetTemplateChild("PART_RESTORE") as Button;
PART_CLOSE = GetTemplateChild("PART_CLOSE") as Button;
if (PART_RESTORE != null)
PART_RESTORE.Click += PART_RESTORE_Click;
if (PART_MAX != null)
PART_MAX.Click += PART_MAX_Click;
if (PART_MIN != null)
PART_MIN.Click += PART_MIN_Click;
if (PART_CLOSE != null)
PART_CLOSE.Click += PART_CLOSE_Click;
base.OnApplyTemplate();
}
private void PART_CLOSE_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void PART_MIN_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Minimized;
}
private void PART_MAX_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Maximized;
}
private void PART_RESTORE_Click(object sender, RoutedEventArgs e)
{
WindowState = WindowState.Normal;
}
/// <summary>
/// 判断是否为模态窗口
/// </summary>
/// <returns></returns>
public bool IsModal()
{
var filedInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
return filedInfo != null && (bool)filedInfo.GetValue(this);
}
#region PropertyNotify
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool Set<T>(ref T oldValue, T newValue, [CallerMemberName] string propertyName = "")
{
if (Equals(oldValue, newValue))
return false;
oldValue = newValue;
OnPropertyChanged(propertyName);
return true;
}
#endregion
}
}

71
SJ.Controls/Extensions/VisualTreeExtension.cs

@ -0,0 +1,71 @@
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace SJ.Controls.Extensions
{
public static class VisualTreeExtension
{
public static T HitTest<T>(this FrameworkElement fe, Point? point) where T : FrameworkElement
{
if (point == null)
point = Mouse.GetPosition(fe);
var result = VisualTreeHelper.HitTest(fe, point.Value);
if (result == null)
return null;
if (result.VisualHit != null)
{
var r = FindParentOfType<T>(result.VisualHit);
return r;
}
return null;
}
/// <summary>
/// 查找父控件
/// </summary>
/// <typeparam name="T">父控件类型</typeparam>
/// <param name="obj">子控件实例</param>
/// <returns></returns>
public static T FindParentOfType<T>(this DependencyObject obj) where T : FrameworkElement
{
DependencyObject parent = VisualTreeHelper.GetParent(obj);
while (parent != null)
{
if (parent is T)
{
return (T)parent;
}
parent = VisualTreeHelper.GetParent(parent);
}
return null;
}
/// <summary>
/// 查找子控件
/// </summary>
/// <typeparam name="T">需要查找的控件类型</typeparam>
/// <param name="obj">父控件实例</param>
/// <returns></returns>
public static T FindFirstVisualChild<T>(this DependencyObject obj) where T : FrameworkElement
{
var queue = new Queue<DependencyObject>();
queue.Enqueue(obj);
while (queue.Count > 0)
{
DependencyObject current = queue.Dequeue();
for (int i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
{
DependencyObject child = VisualTreeHelper.GetChild(current, i);
if (child != null && child is T)
{
return (T)child;
}
queue.Enqueue(child);
}
}
return null;
}
}
}

188
SJ.Controls/Helpers/StoryboardHelper.cs

@ -0,0 +1,188 @@
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media.Animation;
namespace SJ.Controls.Helpers
{
public class StoryboardHelper
{
/// <summary>
/// 播放动画
/// </summary>
/// <param name="fe">控件源</param>
/// <param name="from">开始值</param>
/// <param name="to">结束值</param>
/// <param name="duration">时间间隔</param>
/// <param name="IsAutoReverse">是否反向播放</param>
/// <param name="RepeatPlay">是否重复播放</param>
/// <param name="ef">动画类型</param>
/// <param name="Callback">回调函数</param>
/// <param name="PropertyPath">动画属性</param>
public static void _PlayDoubleAnimation(FrameworkElement fe, double from, double to, TimeSpan duration, bool IsAutoReverse, bool RepeatPlay, IEasingFunction ef, Action Callback, string PropertyPath)
{
Storyboard _sb = new Storyboard();
_sb.Completed += new EventHandler((s, e) =>
{
if (Callback != null)
Callback();
_sb.Stop();
_sb.Children.Clear();
_sb = null;
});
DoubleAnimation daRotation = new DoubleAnimation();
daRotation.From = from;
daRotation.To = to;
daRotation.EasingFunction = ef;
daRotation.Duration = duration;
Storyboard.SetTargetProperty(daRotation, new PropertyPath(PropertyPath));
Storyboard.SetTarget(daRotation, fe);
_sb.Children.Add(daRotation);
_sb.AutoReverse = IsAutoReverse;
if (RepeatPlay)
_sb.RepeatBehavior = RepeatBehavior.Forever;
_sb.Begin();
}
public static void _PlayAnimationUsingKeyFrames(IList<AnimationModel> AnimationUsingKeyFrameList, bool IsAutoReverse, bool IsRepeayPlay, Action Callback)
{
if (AnimationUsingKeyFrameList == null || AnimationUsingKeyFrameList.Count == 0)
return;
Storyboard _sb = new Storyboard();
_sb.Completed += new EventHandler((s, e) =>
{
if (Callback != null)
Callback();
_sb.Stop();
_sb.Children.Clear();
_sb = null;
});
_sb.AutoReverse = IsAutoReverse;
if (IsRepeayPlay)
_sb.RepeatBehavior = RepeatBehavior.Forever;
foreach (AnimationModel am in AnimationUsingKeyFrameList)
{
AnimationTimeline animationTimeLine = null;
switch (am._KeyFrameType)
{
case KeyFrameType.DoubleKeyFrame:
animationTimeLine = CreateDoubleAnimationUsingKeyFrames(am);
break;
case KeyFrameType.ColorKeyFrame:
animationTimeLine = CreateColorAnimationUsingKeyFrames(am);
break;
case KeyFrameType.ObjectKeyFrame:
animationTimeLine = CreateObjectAnimationUsingKeyFrames(am);
break;
}
_sb.Children.Add(animationTimeLine);
}
_sb.Begin();
}
private static AnimationTimeline CreateDoubleAnimationUsingKeyFrames(AnimationModel am)
{
DoubleAnimationUsingKeyFrames animationTimeline = new DoubleAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(animationTimeline, new PropertyPath(am.PropertyPath));
Storyboard.SetTarget(animationTimeline, am.Element);
foreach (BaseKeyFrame baseKeyFrame in am.KeyFrames)
{
animationTimeline.KeyFrames.Add(
new EasingDoubleKeyFrame(
Convert.ToInt32(baseKeyFrame.Value),
baseKeyFrame._KeyTime,
baseKeyFrame.EasingFunction)
);
}
return animationTimeline;
}
private static AnimationTimeline CreateColorAnimationUsingKeyFrames(AnimationModel am)
{
ColorAnimationUsingKeyFrames animationTimeline = new ColorAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(animationTimeline, new PropertyPath(am.PropertyPath));
Storyboard.SetTarget(animationTimeline, am.Element);
foreach (BaseKeyFrame baseKeyFrame in am.KeyFrames)
{
animationTimeline.KeyFrames.Add(
new EasingColorKeyFrame(
(System.Windows.Media.Color)baseKeyFrame.Value,
baseKeyFrame._KeyTime,
baseKeyFrame.EasingFunction)
);
}
return animationTimeline;
}
private static AnimationTimeline CreateObjectAnimationUsingKeyFrames(AnimationModel am)
{
ObjectAnimationUsingKeyFrames animationTimeline = new ObjectAnimationUsingKeyFrames();
Storyboard.SetTargetProperty(animationTimeline, new PropertyPath(am.PropertyPath));
Storyboard.SetTarget(animationTimeline, am.Element);
foreach (BaseKeyFrame baseKeyFrame in am.KeyFrames)
{
animationTimeline.KeyFrames.Add(
new DiscreteObjectKeyFrame(
baseKeyFrame.Value,
baseKeyFrame._KeyTime)
);
}
return animationTimeline;
}
}
/// <summary>
/// 关键帧动画类型
/// </summary>
public enum KeyFrameType
{
DoubleKeyFrame = 1,
ColorKeyFrame = 2,
ObjectKeyFrame = 3
}
public class AnimationModel
{
public AnimationModel()
{
this.KeyFrames = new List<BaseKeyFrame>();
}
/// <summary>
/// 执行动画的对象
/// </summary>
public FrameworkElement Element;
/// <summary>
/// 作用于动画的属性
/// </summary>
public string PropertyPath;
/// <summary>
/// 动画类型枚举
/// </summary>
public KeyFrameType _KeyFrameType;
/// <summary>
/// 关键帧动画帧集合
/// </summary>
public IList<BaseKeyFrame> KeyFrames;
}
public class BaseKeyFrame
{
/// <summary>
/// 动画触发时间
/// </summary>
public TimeSpan _KeyTime;
/// <summary>
/// 值
/// </summary>
public object Value;
/// <summary>
/// 缓动函数类型
/// </summary>
public IEasingFunction EasingFunction;
}
}

24
SJ.Controls/PageControl.xaml

@ -0,0 +1,24 @@
<UserControl x:Class="SJ.Controls.PageControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:c="clr-namespace:SJ.Controls"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="500">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding PageSize,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type c:PageControl}},StringFormat=每页\{0\}条}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding PageIndex,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type c:PageControl}},StringFormat=当前显示第\{0\}}" VerticalAlignment="Center" Margin="15,0,0,0"/>
<TextBlock Text="{Binding PageCount,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type c:PageControl}},StringFormat=/\{0\}页}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding RecordCount,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type c:PageControl}},StringFormat=共\{0\}条记录}" VerticalAlignment="Center" Margin="15,0,0,0"/>
<c:BButton x:Name="btn_first" Content="首页" Margin="15,0,0,0" VerticalAlignment="Center" Click="btn_first_Click"
Style="{StaticResource LinkButton}"/>
<c:BButton x:Name="btn_up" Margin="15,0,0,0" VerticalAlignment="Center" Click="btn_up_Click" Content="上一页"
Style="{StaticResource LinkButton}"/>
<!--<StackPanel x:Name="sp_pagenumber" Orientation="Horizontal" VerticalAlignment="Center"/>-->
<c:BButton x:Name="btn_next" Margin="15,0,0,0" VerticalAlignment="Center" Click="btn_next_Click" Content="下一页"
Style="{StaticResource LinkButton}"/>
<c:BButton x:Name="btn_last" Content="尾页" Margin="15,0,0,0" VerticalAlignment="Center" Click="btn_last_Click"
Style="{StaticResource LinkButton}"/>
</StackPanel>
</UserControl>

137
SJ.Controls/PageControl.xaml.cs

@ -0,0 +1,137 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace SJ.Controls
{
/// <summary>
/// PageControl.xaml 的交互逻辑
/// </summary>
public partial class PageControl : UserControl
{
public PageControl()
{
InitializeComponent();
this.Loaded += PageControl_Loaded;
}
private void PageControl_Loaded(object sender, RoutedEventArgs e)
{
this.PageIndex = 1;
pageArgs.PageIndex = this.PageIndex;
}
/// <summary>
/// 分页事件
/// </summary>
public event RoutedEventHandler OnPageIndexChanged;
/// <summary>
/// 分页参数
/// </summary>
private PageArgs pageArgs = new PageArgs();
public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register("PageIndex", typeof(int), typeof(PageControl), new PropertyMetadata(1, new PropertyChangedCallback((s, e) =>
{
var pageControl = s as PageControl;
pageControl?.OnIndexChanged();
})));
/// <summary>
/// 当前页数
/// </summary>
public int PageIndex
{
get { return (int)GetValue(PageIndexProperty); }
set { SetValue(PageIndexProperty, value); }
}
public static readonly DependencyProperty PageSizeProperty = DependencyProperty.Register("PageSize", typeof(int), typeof(PageControl), new PropertyMetadata(1));
/// <summary>
/// 每页记录数
/// </summary>
public int PageSize
{
get { return (int)GetValue(PageSizeProperty); }
set { SetValue(PageSizeProperty, value); }
}
public static readonly DependencyProperty PageCountProperty = DependencyProperty.Register("PageCount", typeof(int), typeof(PageControl), new PropertyMetadata(1));
/// <summary>
/// 总页数
/// </summary>
public int PageCount
{
get { return (int)GetValue(PageCountProperty); }
set { SetValue(PageCountProperty, value); }
}
public static readonly DependencyProperty RecordCountProperty = DependencyProperty.Register("RecordCount", typeof(int), typeof(PageControl), new PropertyMetadata(0, new PropertyChangedCallback((s, e) =>
{
var pageControl = s as PageControl;
pageControl?.OnRecordChanged();
})));
/// <summary>
/// 总记录数
/// </summary>
public int RecordCount
{
get { return (int)GetValue(RecordCountProperty); }
set { SetValue(RecordCountProperty, value); }
}
private void OnRecordChanged()
{
PageCount = (RecordCount - 1) / PageSize + 1;
}
private void OnIndexChanged()
{
pageArgs.PageIndex = this.PageIndex;
OnPageIndexChanged?.Invoke(this, pageArgs);
}
private void Btn_Click(object sender, RoutedEventArgs e)
{
var btn = sender as BButton;
var newPageIndex = Convert.ToInt32(btn.Content);
if (newPageIndex != PageIndex)
PageIndex = newPageIndex;
else
OnIndexChanged();
}
private void btn_first_Click(object sender, RoutedEventArgs e)
{
if (PageIndex > 1)
PageIndex = 1;
}
private void btn_up_Click(object sender, RoutedEventArgs e)
{
if (PageIndex > 1)
PageIndex--;
}
private void btn_next_Click(object sender, RoutedEventArgs e)
{
if (PageIndex < PageCount)
PageIndex++;
}
private void btn_last_Click(object sender, RoutedEventArgs e)
{
if (PageIndex < PageCount)
PageIndex = PageCount;
}
}
public class PageArgs : RoutedEventArgs
{
public int PageIndex;
//其余自行扩展
}
}

56
SJ.Controls/RoundWaitProgress.xaml

@ -0,0 +1,56 @@
<UserControl x:Class="SJ.Controls.RoundWaitProgress"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SJ.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Width="{Binding AnimationSize,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" Height="{Binding Width,RelativeSource={RelativeSource Self}}">
<Grid x:Name="g1" RenderTransformOrigin="0.5,0.5" Opacity="0">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
</Grid.RenderTransform>
<Border Width="8" Height="8" Background="{Binding Color,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
<Grid x:Name="g2" RenderTransformOrigin="0.5,0.5" Opacity="0">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
</Grid.RenderTransform>
<Border Width="8" Height="8" Background="{Binding Color,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
<Grid x:Name="g3" RenderTransformOrigin="0.5,0.5" Opacity="0">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
</Grid.RenderTransform>
<Border Width="8" Height="8" Background="{Binding Color,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
</Grid>
<Grid x:Name="g4" RenderTransformOrigin="0.5,0.5" Opacity="0">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
</Grid.RenderTransform>
<Border Width="8" Height="8" Background="{Binding Color,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
<Grid x:Name="g5" RenderTransformOrigin="0.5,0.5" Opacity="0">
<Grid.RenderTransform>
<RotateTransform Angle="0"/>
</Grid.RenderTransform>
<Border Width="8" Height="8" Background="{Binding Color,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}" CornerRadius="5" VerticalAlignment="Bottom" HorizontalAlignment="Center"/>
</Grid>
</Grid>
<TextBlock Text="{Binding WaitText,Mode=OneWay,UpdateSourceTrigger=PropertyChanged,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:RoundWaitProgress}}}"
Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
Margin="0,15,0,0"/>
</Grid>
</Grid>
</UserControl>

180
SJ.Controls/RoundWaitProgress.xaml.cs

@ -0,0 +1,180 @@
using SJ.Controls.Helpers;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace SJ.Controls
{
/// <summary>
/// RoundWaitProgress.xaml 的交互逻辑
/// </summary>
public partial class RoundWaitProgress : UserControl
{
public RoundWaitProgress()
{
InitializeComponent();
this.Loaded += RoundWaitProgress_Loaded;
InitAnimationData();
}
private void RoundWaitProgress_Loaded(object sender, RoutedEventArgs e)
{
if (!IsPlaying)
this.Visibility = Visibility.Collapsed;
}
/// <summary>
/// 初始化动画数据
/// </summary>
private void InitAnimationData()
{
if (ControlList == null)
ControlList = new List<FrameworkElement>() { g1, g2, g3, g4, g5 };
if (AngleAnimationParamList == null)
AngleAnimationParamList = new List<BaseKeyFrame>();
if (BeginOpacityAnimationParamList == null)
BeginOpacityAnimationParamList = new List<BaseKeyFrame>();
if (EndOpacityAnimationParamList == null)
EndOpacityAnimationParamList = new List<BaseKeyFrame>();
AngleAnimationParamList.Clear();
BeginOpacityAnimationParamList.Clear();
EndOpacityAnimationParamList.Clear();
AngleAnimationParamList.Add(new BaseKeyFrame() { Value = 0, _KeyTime = new TimeSpan(0, 0, 0, 0, 0) });
AngleAnimationParamList.Add(new BaseKeyFrame() { Value = 112, _KeyTime = new TimeSpan(0, 0, 0, 0, 400) });
AngleAnimationParamList.Add(new BaseKeyFrame() { Value = 202, _KeyTime = new TimeSpan(0, 0, 0, 0, 1500) });
AngleAnimationParamList.Add(new BaseKeyFrame() { Value = 472, _KeyTime = new TimeSpan(0, 0, 0, 0, 2200) });
AngleAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 3100), Value = 562 });
AngleAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 3500), Value = 720 });
BeginOpacityAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 0), Value = 0 });
BeginOpacityAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 1), Value = 1 });
EndOpacityAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 3499), Value = 1 });
EndOpacityAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 3500), Value = 0 });
EndOpacityAnimationParamList.Add(new BaseKeyFrame() { _KeyTime = new TimeSpan(0, 0, 0, 0, 4500), Value = 0 });
}
/// <summary>
/// 动画属性
/// </summary>
public static readonly string AnglePropertyPath = "(UIElement.RenderTransform).(RotateTransform.Angle)";
public static readonly string OpacityPropertyPath = "(UIElement.Opacity)";
private IList<BaseKeyFrame> AngleAnimationParamList;
private IList<BaseKeyFrame> BeginOpacityAnimationParamList;
private IList<BaseKeyFrame> EndOpacityAnimationParamList;
private IList<FrameworkElement> ControlList;
private Storyboard _Storyboard;
private bool IsPlaying;
/// <summary>
/// 间隔时间
/// </summary>
public static readonly TimeSpan IntervalTimeSpan = new TimeSpan(0, 0, 0, 0, 200);
public static readonly DependencyProperty WaitTextProperty = DependencyProperty.Register("WaitText", typeof(string), typeof(RoundWaitProgress), new PropertyMetadata("正在加载数据"));
public string WaitText
{
get { return GetValue(WaitTextProperty).ToString(); }
set { SetValue(WaitTextProperty, value); }
}
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register("Color", typeof(Brush), typeof(RoundWaitProgress), new PropertyMetadata(new SolidColorBrush(Colors.Black)));
public Brush Color
{
get { return GetValue(ColorProperty) as Brush; }
set { SetValue(ColorProperty, value); }
}
public static readonly DependencyProperty AnimationSizeProperty = DependencyProperty.Register("AnimationSize", typeof(double), typeof(RoundWaitProgress), new PropertyMetadata(80.0));
public double AnimationSize
{
get { return Convert.ToDouble(GetValue(AnimationSizeProperty)); }
set { SetValue(AnimationSizeProperty, value); }
}
public static readonly DependencyProperty PlayProperty = DependencyProperty.Register("Play", typeof(bool), typeof(RoundWaitProgress), new PropertyMetadata(false, (s, e) =>
{
var waitControl = s as RoundWaitProgress;
if (waitControl.Play)
waitControl.Start();
else
waitControl.Stop();
}));
public bool Play
{
get { return (bool)GetValue(PlayProperty); }
set { SetValue(PlayProperty, value); }
}
private void Start()
{
this.IsPlaying = true;
this.Visibility = Visibility.Visible;
this._Storyboard = new Storyboard();
foreach (var frameElement in ControlList)
{
(frameElement.RenderTransform as RotateTransform).Angle = 0;
DoubleAnimationUsingKeyFrames daukf = new DoubleAnimationUsingKeyFrames();
foreach (var item in AngleAnimationParamList)
{
daukf.KeyFrames.Add(new EasingDoubleKeyFrame(Convert.ToDouble(item.Value), item._KeyTime));
}
Storyboard.SetTargetProperty(daukf, new PropertyPath(AnglePropertyPath));
Storyboard.SetTarget(daukf, frameElement);
this._Storyboard.Children.Add(daukf);
DoubleAnimationUsingKeyFrames daukf1 = new DoubleAnimationUsingKeyFrames();
foreach (var item in BeginOpacityAnimationParamList)
{
daukf1.KeyFrames.Add(new EasingDoubleKeyFrame(Convert.ToDouble(item.Value), item._KeyTime));
}
foreach (var item in EndOpacityAnimationParamList)
{
daukf1.KeyFrames.Add(new EasingDoubleKeyFrame(Convert.ToDouble(item.Value), item._KeyTime));
}
Storyboard.SetTargetProperty(daukf1, new PropertyPath(OpacityPropertyPath));
Storyboard.SetTarget(daukf1, frameElement);
this._Storyboard.Children.Add(daukf1);
for (var i = 0; i < AngleAnimationParamList.Count; i++)
{
var item = AngleAnimationParamList[i];
item._KeyTime = item._KeyTime.Add(IntervalTimeSpan);
}
foreach (var item in BeginOpacityAnimationParamList)
{
item._KeyTime = item._KeyTime.Add(IntervalTimeSpan);
}
foreach (var item in EndOpacityAnimationParamList)
{
item._KeyTime = item._KeyTime.Add(IntervalTimeSpan);
}
this._Storyboard.RepeatBehavior = RepeatBehavior.Forever;
}
this._Storyboard.Begin();
}
private void Stop()
{
this._Storyboard.Stop();
this._Storyboard.Children.Clear();
this._Storyboard = null;
this.IsPlaying = false;
InitAnimationData();
this.Visibility = Visibility.Collapsed;
}
}
}

17
SJ.Controls/SJ.Controls.csproj

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.Drawing.Common" Version="6.0.2-mauipre.1.22102.15" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\QYMessageCenter.Common\QYMessageCenter.Common.csproj" />
</ItemGroup>
</Project>

377
SJ.Controls/Themes/Generic.xaml

@ -0,0 +1,377 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SJ.Controls">
<Style x:Key="BWin_MIN"
TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" Background="Transparent"
BorderThickness="0">
<Path x:Name="p" Stretch="Uniform" SnapsToDevicePixels="True"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="12" Height="2" Data="M0,0 12,0 12,1 0,1z" Fill="{TemplateBinding Foreground}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BWin_MAX"
TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border"
Background="#00FFFFFF"
BorderThickness="0">
<Path x:Name="p" Stretch="Uniform" Width="12" Height="12" Data="M1,0 10,0 11,1 11,10 10,11 1,11 0,10 0,1z" Stroke="{TemplateBinding Foreground}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BWin_RESTORE"
TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border"
Background="#00FFFFFF"
BorderThickness="0">
<Path x:Name="p" Stretch="Uniform" SnapsToDevicePixels="True" UseLayoutRounding="True" Data="M9,9 L9,9 9,12 L8,13 1,13 L0,12 0,5 L1,4 4,4 L4,3 4,1 L5,0 12,0 L13,1 13,8 L12,9 10,9 L9,9 9,5 L8,4 4,4" Width="14" Height="14" Stroke="{TemplateBinding Foreground}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="BWin_CLOSE"
TargetType="{x:Type Button}">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="VerticalContentAlignment"
Value="Center" />
<Setter Property="Foreground" Value="White"/>
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border"
Background="#00FFFFFF"
BorderThickness="0">
<Path x:Name="p" Stretch="Uniform" Width="12" Height="12"
Data="M814.060 781.227q-67.241-67.241-269.773-269.773 67.241-67.241 269.773-269.773 5.671-6.481 5.671-12.962 0 0-0.81-0.81 0-6.481-4.861-9.722-4.861-4.051-11.342-4.861-0.81 0-0.81 0-5.671 0-11.342 4.861-89.924 89.924-269.773 269.773-67.241-67.241-269.773-269.773-4.861-4.861-12.962-4.861-7.291 0.81-10.532 4.861-5.671 5.671-5.671 11.342 0 6.481 5.671 12.152 89.924 89.924 269.773 269.773-67.241 67.241-269.773 269.773-11.342 11.342 0 23.494 12.152 11.342 23.494 0 89.924-89.924 269.773-269.773 67.241 67.241 269.773 269.773 5.671 5.671 11.342 5.671 5.671 0 12.152-5.671 4.861-5.671 4.861-12.962 0-6.481-4.861-10.532z"
Fill="{TemplateBinding Foreground}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:BWindow}">
<Setter Property="Background" Value="White"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BWindow}">
<Border SnapsToDevicePixels="True"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}">
<Grid x:Name="win_content">
<AdornerDecorator>
<ContentPresenter/>
</AdornerDecorator>
<StackPanel Panel.ZIndex="99" Orientation="Horizontal"
Margin="{Binding Path=RightButtonGroupMargin,RelativeSource={RelativeSource Mode=TemplatedParent}}"
VerticalAlignment="Top" Height="22"
HorizontalAlignment="Right"
WindowChrome.IsHitTestVisibleInChrome="True">
<Button WindowChrome.IsHitTestVisibleInChrome="True"
x:Name="PART_MIN"
Width="24" Height="22"
Style="{StaticResource BWin_MIN}"
Foreground="{Binding MinButtonColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"
Visibility="{Binding Path=MinButtonVisibility, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"/>
<Grid Visibility="{Binding Path=MaxButtonVisibility, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
<Button WindowChrome.IsHitTestVisibleInChrome="True" x:Name="PART_MAX" Width="22" Height="22" Style="{StaticResource BWin_MAX}"
Foreground="{Binding MaxButtonColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<Button WindowChrome.IsHitTestVisibleInChrome="True" x:Name="PART_RESTORE" Width="22" Height="22" Style="{StaticResource BWin_RESTORE}"
Foreground="{Binding MaxButtonColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Grid>
<Button WindowChrome.IsHitTestVisibleInChrome="True"
x:Name="PART_CLOSE"
Width="24" Height="22"
Style="{StaticResource BWin_CLOSE}"
Foreground="{Binding CloseButtonColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"
Visibility="{Binding Path=CloseButtonVisibility, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"></Button>
</StackPanel>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="Visibility" Value="Collapsed" TargetName="PART_MAX"/>
<Setter Property="Visibility" Value="Visible" TargetName="PART_RESTORE"/>
<Setter Property="Margin" Value="8" TargetName="win_content"/>
</Trigger>
<Trigger Property="WindowState" Value="Normal">
<Setter Property="Visibility" Value="Visible" TargetName="PART_MAX"/>
<Setter Property="Visibility" Value="Collapsed" TargetName="PART_RESTORE"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:BButton}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<!--<Setter Property="FontFamily" Value="Comic Sans MS" />-->
<Setter Property="BorderCornerRadius" Value="0" />
<Setter Property="Background" Value="#02FFFFFF"/>
<Setter Property="DisableBgColor" Value="{Binding Background,RelativeSource={RelativeSource Self}}"/>
<Setter Property="MouseOverBgColor" Value="{Binding Background,RelativeSource={RelativeSource Self}}" />
<Setter Property="MouseOverFontColor" Value="{Binding Foreground,RelativeSource={RelativeSource Self}}" />
<Setter Property="PressedBgColor" Value="{Binding MouseOverBgColor,RelativeSource={RelativeSource Self}}" />
<Setter Property="PressedFontColor" Value="{Binding MouseOverFontColor,RelativeSource={RelativeSource Self}}" />
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="Cursor" Value="Hand" />
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BButton}">
<Border x:Name="Bd" RenderTransformOrigin="0.5,0.5"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding BorderCornerRadius}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="True">
<Border.RenderTransform>
<ScaleTransform ScaleX="1" ScaleY="1"/>
</Border.RenderTransform>
<ContentPresenter x:Name="btnContent" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding MouseOverBgColor,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="Bd" />
<Setter Property="TextBlock.Foreground" Value="{Binding MouseOverFontColor,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="Bd" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{Binding PressedBgColor,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="Bd" />
<Setter Property="TextBlock.Foreground" Value="{Binding PressedFontColor,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="Bd" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsPressed" Value="True"/>
<Condition Property="PressedScale" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="RenderTransform" TargetName="Bd">
<Setter.Value>
<ScaleTransform ScaleX="0.93" ScaleY="0.93"/>
</Setter.Value>
</Setter>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
<Setter Property="Background" Value="{Binding DisableBgColor,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="Bd" />
<Setter Property="Content" Value="{Binding DisableText,RelativeSource={RelativeSource Mode=TemplatedParent}}" TargetName="btnContent" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:BTextBox}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Cursor" Value="IBeam"/>
<Setter Property="Padding" Value="5,0,0,0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="WaterRemarkFontColor" Value="Gray"/>
<Setter Property="DisableBgColor" Value="Gray"/>
<Setter Property="Height" Value="30"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BTextBox}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding BorderCornerRadius}"
SnapsToDevicePixels="True">
<Grid>
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
<TextBlock x:Name="txtRemark" Text="{TemplateBinding WaterRemark}"
Foreground="{TemplateBinding WaterRemarkFontColor}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"
Visibility="Collapsed"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Visible" TargetName="txtRemark"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{Binding DisableBgColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"
TargetName="border"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:BTextBoxAnimation}">
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="BorderThickness" Value="0,0,0,1"/>
<Setter Property="BorderBrush" Value="Gray"/>
<Setter Property="Cursor" Value="IBeam"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="Padding" Value="5,0,0,0"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}" />
<Setter Property="Focusable" Value="True"/>
<Setter Property="WaterRemarkFontColor" Value="Gray"/>
<Setter Property="WaterRemarkTopStateColor" Value="Gray"/>
<Setter Property="DisableBgColor" Value="Gray"/>
<Setter Property="MinHeight" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BTextBoxAnimation}">
<Border x:Name="border"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding BorderCornerRadius}"
SnapsToDevicePixels="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Focusable="False" IsTabStop="False"
HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"
Grid.Row="1"/>
<TextBlock x:Name="txtRemark"
Text="{TemplateBinding WaterRemark}"
Foreground="{TemplateBinding WaterRemarkFontColor}"
VerticalAlignment="Center"
Margin="{TemplateBinding Padding}"
Grid.Row="1"
Panel.ZIndex="2"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{Binding DisableBgColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"
TargetName="border"/>
</Trigger>
<Trigger Property="WaterRemarkState"
Value="Top">
<Setter TargetName="txtRemark" Property="Foreground" Value="{Binding WaterRemarkTopStateColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<Setter TargetName="border" Property="BorderBrush" Value="{Binding WaterRemarkTopStateColor,RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:BAsyncImage}">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="UseLayoutRounding" Value="True"/>
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BAsyncImage}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid>
<!-- Source="{TemplateBinding ImageSource}"-->
<Image x:Name="image"
Stretch="{TemplateBinding Stretch}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="{TemplateBinding LoadingText}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{TemplateBinding FontFamily}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Name="txtLoading"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsLoading" Value="False">
<Setter Property="Visibility"
Value="Collapsed"
TargetName="txtLoading"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="middleTextBlock" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</ResourceDictionary>
Loading…
Cancel
Save