C# maui暂时没有官方支持InkCanvas,但是不影响,自己实现一个就行了。目前支持,画图,选择,移动和删除。同时支持自定义橡皮擦形状,也支持绑定自定义的形状列表。

实现一个Converter类,以后所有的绑定类型转换都在这个类中实现。

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks; namespace Shares.Utility
{
public class Converter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
// Implement conversion logic here
if (value is List<string> list)
{
return string.Join(", ", list); // 自定义分隔符
}
else if (value is int intValue && targetType.IsEnum)
{
return Enum.ToObject(targetType, intValue); // 将整数转换为枚举类型
}
return value;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
{
// Implement conversion back logic here
return value;
}
}
}

然后在MyStyles.xaml中添加Converter类的引用,这样以后所有项目都可以使用了,local是

xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"

    <!--converter定义-->
<local:Converter x:Key="Converter"/>

InkCanvas重写GraphicsView

    public class InkCanvas : GraphicsView, IDrawable
{
public class DrawingPath
{
private RectF? cachedBounds;
private bool isDirty = true; public Guid Id { get; } = Guid.NewGuid();
public PathF Path { get; set; } = new PathF();
public Color? StrokeColor { get; set; }
public float StrokeThickness { get; set; }
public bool IsSelected { get; set; }
public PointF Pos { get; set; } public RectF Bounds
{
get
{
if (!isDirty && cachedBounds.HasValue)
return cachedBounds.Value; if (Path.Count == 0)
{
cachedBounds = RectF.Zero;
return RectF.Zero;
} var points = Path.Points;
float minX = float.MaxValue, minY = float.MaxValue;
float maxX = float.MinValue, maxY = float.MinValue; foreach (var point in points)
{
float x = point.X + Pos.X;
float y = point.Y + Pos.Y;
minX = Math.Min(minX, x);
minY = Math.Min(minY, y);
maxX = Math.Max(maxX, x);
maxY = Math.Max(maxY, y);
} cachedBounds = new RectF(minX, minY, maxX - minX, maxY - minY);
isDirty = false;
return cachedBounds.Value;
}
} public void InvalidateBounds() => isDirty = true; public void LineTo(float x, float y)
{
Path.LineTo(x, y);
InvalidateBounds();
} public bool IntersectAt(PointF eraserPos, float eraserRadius)
{
if (Path.Count == 0)
return false; // 优化点接触检查
foreach (var point in Path.Points)
{
float dx = point.X + Pos.X - eraserPos.X;
float dy = point.Y + Pos.Y - eraserPos.Y;
if (dx * dx + dy * dy <= eraserRadius * eraserRadius)
{
return true;
}
} // 优化线段接触检查
if (Path.Count >= 2)
{
var points = Path.Points;
for (int i = 1; i < points.Count(); i++)
{
var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);
var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y); if (PointToLineDistance(start, end, eraserPos) <= eraserRadius)
{
return true;
}
}
} return false;
} public List<DrawingPath> SplitAt(PointF eraserPos, float eraserRadius)
{
var newPaths = new List<DrawingPath>();
if (Path.Count < 2) return newPaths; var points = Path.Points;
int bestIndex = -1;
float minDistance = float.MaxValue; // 1. 检查点接触
for (int i = 0; i < points.Count(); i++)
{
float dx = points.ElementAt(i).X + Pos.X - eraserPos.X;
float dy = points.ElementAt(i).Y + Pos.Y - eraserPos.Y;
float distance = dx * dx + dy * dy; if (distance < minDistance)
{
minDistance = distance;
bestIndex = i;
}
} // 点接触处理
if (bestIndex >= 0 && minDistance <= eraserRadius * eraserRadius)
{
// 起点处理
if (bestIndex == 0)
{
if (points.Count() > 1)
{
var newPath = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
newPath.Path.MoveTo(points.ElementAt(1));
for (int i = 2; i < points.Count(); i++)
{
newPath.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(newPath);
}
return newPaths;
} // 终点处理
if (bestIndex == points.Count() - 1)
{
if (points.Count() > 1)
{
var newPath = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
newPath.Path.MoveTo(points.ElementAt(0));
for (int i = 1; i < points.Count() - 1; i++)
{
newPath.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(newPath);
}
return newPaths;
} // 中间点处理
if (bestIndex > 0 && bestIndex < points.Count() - 1)
{
// 第一段路径
var path1 = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
path1.Path.MoveTo(points.ElementAt(0));
for (int i = 1; i <= bestIndex; i++)
{
path1.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(path1); // 第二段路径
var path2 = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
path2.Path.MoveTo(points.ElementAt(bestIndex));
for (int i = bestIndex + 1; i < points.Count(); i++)
{
path2.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(path2); return newPaths;
}
} // 2. 线段接触处理
bestIndex = -1;
minDistance = float.MaxValue; for (int i = 1; i < points.Count(); i++)
{
var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);
var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y); float distance = PointToLineDistance(start, end, eraserPos);
if (distance < minDistance)
{
minDistance = distance;
bestIndex = i;
}
} if (bestIndex > 0 && minDistance <= eraserRadius)
{
// 第一段路径
if (bestIndex > 1)
{
var path1 = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
path1.Path.MoveTo(points.ElementAt(0));
for (int i = 1; i < bestIndex; i++)
{
path1.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(path1);
} // 第二段路径
if (bestIndex < points.Count() - 1)
{
var path2 = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = Pos
};
path2.Path.MoveTo(points.ElementAt(bestIndex));
for (int i = bestIndex + 1; i < points.Count(); i++)
{
path2.Path.LineTo(points.ElementAt(i));
}
newPaths.Add(path2);
}
} return newPaths;
}
} public enum InkCanvasEditingMode { Ink, Select, Erase } public static readonly BindableProperty EditingModeProperty =
BindableProperty.Create(nameof(EditingMode), typeof(InkCanvasEditingMode), typeof(InkCanvas),
InkCanvasEditingMode.Ink, BindingMode.TwoWay, propertyChanged: OnEditingModeChanged); private static void OnEditingModeChanged(BindableObject bindable, object oldValue, object newValue)
{
if (bindable is InkCanvas canvas)
{
canvas.ClearSelection();
canvas.Invalidate();
}
} public InkCanvasEditingMode EditingMode
{
get => (InkCanvasEditingMode)GetValue(EditingModeProperty);
set => SetValue(EditingModeProperty, value);
} public ObservableCollection<DrawingPath> Paths { get; set; } = new ObservableCollection<DrawingPath>();
public DrawingPath Eraser { get; }
public float EraserRadius { get; set; } = 15f; // 增大橡皮擦半径
private DrawingPath? currentPath;
private RectF? selectionRect;
private PointF lastTouchPoint;
private bool isMovingSelection; // 橡皮擦轨迹跟踪
private readonly List<PointF> eraserTrail = new List<PointF>();
private const int MaxEraserTrailPoints = 5; public Color StrokeColor { get; set; } = Colors.Black;
public Color SelectionColor { get; set; } = Colors.Red;
public float SelectionStrokeThickness { get; set; } = 1f;
public float StrokeThickness { get; set; } = 1f; public InkCanvas()
{
Drawable = this;
BackgroundColor = Colors.Transparent;
Eraser = CreateEraserPath(); StartInteraction += OnTouchStarted;
DragInteraction += OnTouchMoved;
EndInteraction += OnTouchEnded;
} private DrawingPath CreateEraserPath()
{
var path = new PathF();
var points = new[]
{
new PointF(107.4f, 13), new PointF(113.7f, 28.8f),
new PointF(127.9f, 31.3f), new PointF(117.6f, 43.5f),
new PointF(120.1f, 60.8f), new PointF(107.4f, 52.6f),
new PointF(94.6f, 60.8f), new PointF(97.1f, 43.5f),
new PointF(86.8f, 31.3f), new PointF(101f, 28.8f)
}; path.MoveTo(points[0]);
for (int i = 1; i < points.Length; i++)
{
path.LineTo(points[i]);
}
path.Close(); return new DrawingPath { Path = path, StrokeColor = Colors.Black, StrokeThickness = 1f };
} private void OnTouchStarted(object? sender, TouchEventArgs e)
{
if (e.Touches.Length == 0) return; var point = e.Touches[0];
lastTouchPoint = new PointF(point.X, point.Y);
eraserTrail.Clear(); // 清除历史轨迹 switch (EditingMode)
{
case InkCanvasEditingMode.Ink:
StartInking(lastTouchPoint);
break; case InkCanvasEditingMode.Select:
StartSelection(lastTouchPoint);
break; case InkCanvasEditingMode.Erase:
StartErase(lastTouchPoint);
eraserTrail.Add(lastTouchPoint); // 添加起始点
break;
}
Invalidate();
} private void StartInking(PointF startPoint)
{
currentPath = new DrawingPath
{
StrokeColor = StrokeColor,
StrokeThickness = StrokeThickness,
Pos = PointF.Zero
};
currentPath.Path.MoveTo(startPoint.X, startPoint.Y);
Paths.Add(currentPath);
} private void StartSelection(PointF startPoint)
{
isMovingSelection = Paths.Any(p => p.IsSelected && p.Bounds.Contains(startPoint)); if (!isMovingSelection)
{
ClearSelection();
var clickedPath = Paths.LastOrDefault(p => p.Bounds.Contains(startPoint)); if (clickedPath != null)
{
clickedPath.IsSelected = true;
isMovingSelection = true;
}
else
{
selectionRect = new RectF(startPoint, SizeF.Zero);
}
}
} private void StartErase(PointF startPoint)
{
Eraser.Pos = new PointF(startPoint.X - Eraser.Path.Bounds.Width / 2,
startPoint.Y - Eraser.Path.Bounds.Height / 4);
Eraser.IsSelected = true;
} private void OnTouchMoved(object? sender, TouchEventArgs e)
{
if (e.Touches.Length == 0) return; var currentPoint = new PointF(e.Touches[0].X, e.Touches[0].Y); switch (EditingMode)
{
case InkCanvasEditingMode.Ink:
ContinueInking(currentPoint);
break; case InkCanvasEditingMode.Select:
UpdateSelection(currentPoint);
break; case InkCanvasEditingMode.Erase:
UpdateEraser(currentPoint);
ErasePaths();
break;
}
Invalidate();
} private void ContinueInking(PointF currentPoint)
{
if (currentPath == null) return; const float minDistance = 1.0f;
float dx = currentPoint.X - lastTouchPoint.X;
float dy = currentPoint.Y - lastTouchPoint.Y; if (dx * dx + dy * dy > minDistance * minDistance)
{
currentPath.LineTo(currentPoint.X, currentPoint.Y);
lastTouchPoint = currentPoint;
}
} private void UpdateSelection(PointF currentPoint)
{
if (isMovingSelection)
{
MoveSelectedPaths(currentPoint);
}
else if (selectionRect.HasValue)
{
UpdateSelectionRect(currentPoint);
}
} private void UpdateEraser(PointF currentPoint)
{
Eraser.Pos = new PointF(currentPoint.X - Eraser.Path.Bounds.Width / 2,
currentPoint.Y - Eraser.Path.Bounds.Height / 4); // 添加到橡皮擦轨迹
eraserTrail.Add(Eraser.Pos);
if (eraserTrail.Count > MaxEraserTrailPoints)
{
eraserTrail.RemoveAt(0);
} lastTouchPoint = currentPoint;
} // 优化擦除逻辑
private void ErasePaths()
{
// 倒序遍历所有路径
for (int i = Paths.Count - 1; i >= 0; i--)
{
var path = Paths[i]; // 检查橡皮擦轨迹上的所有点
foreach (var trailPoint in eraserTrail)
{
if (path.IntersectAt(trailPoint, EraserRadius))
{
var newPaths = path.SplitAt(trailPoint, EraserRadius); if (newPaths.Count > 0)
{
Paths.RemoveAt(i);
foreach (var newPath in newPaths)
{
if (newPath.Path.Count >= 2) // 只添加有效路径
{
Paths.Add(newPath);
}
}
break; // 路径已被处理,跳出循环
}
else
{
// 没有新路径表示整个路径应被删除
Paths.RemoveAt(i);
break;
}
}
}
}
} private void MoveSelectedPaths(PointF currentPoint)
{
float deltaX = currentPoint.X - lastTouchPoint.X;
float deltaY = currentPoint.Y - lastTouchPoint.Y; foreach (var path in Paths)
{
if (path.IsSelected)
{
path.Pos = new PointF(path.Pos.X + deltaX, path.Pos.Y + deltaY);
path.InvalidateBounds();
}
}
lastTouchPoint = currentPoint;
} private void UpdateSelectionRect(PointF currentPoint)
{
float x = Math.Min(lastTouchPoint.X, currentPoint.X);
float y = Math.Min(lastTouchPoint.Y, currentPoint.Y);
float width = Math.Abs(currentPoint.X - lastTouchPoint.X);
float height = Math.Abs(currentPoint.Y - lastTouchPoint.Y); selectionRect = new RectF(x, y, width, height);
} private void OnTouchEnded(object? sender, TouchEventArgs e)
{
switch (EditingMode)
{
case InkCanvasEditingMode.Select when selectionRect.HasValue:
FinalizeSelection();
break;
} currentPath = null;
selectionRect = null;
isMovingSelection = false;
Eraser.IsSelected = false;
eraserTrail.Clear(); // 清除橡皮擦轨迹
Invalidate();
} private void FinalizeSelection()
{
var selection = selectionRect!.Value; foreach (var path in Paths)
{
if (!selection.IntersectsWith(path.Bounds)) continue; if (selection.Contains(path.Bounds))
{
path.IsSelected = true;
continue;
} foreach (var point in path.Path.Points)
{
var absolutePoint = new PointF(point.X + path.Pos.X, point.Y + path.Pos.Y);
if (selection.Contains(absolutePoint))
{
path.IsSelected = true;
break;
}
}
}
} public void ClearSelection()
{
foreach (var path in Paths)
{
path.IsSelected = false;
}
} public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = BackgroundColor;
canvas.FillRectangle(dirtyRect); canvas.StrokeLineCap = LineCap.Round;
canvas.StrokeLineJoin = LineJoin.Round; // 绘制所有路径
foreach (var path in Paths)
{
// 跳过无效路径(少于2个点)
if (path.Path.Count < 2) continue; DrawPath(canvas, path);
} // 绘制橡皮擦(如果被选中)
if (Eraser.IsSelected)
{
DrawEraser(canvas);
} // 绘制选择框
if (selectionRect.HasValue)
{
DrawSelectionRect(canvas, selectionRect.Value);
}
} private void DrawPath(ICanvas canvas, DrawingPath path)
{
var strokeColor = path.StrokeColor ?? Colors.Black;
float strokeSize = path.IsSelected ? path.StrokeThickness * 1.5f : path.StrokeThickness; if (!path.IsSelected)
{
strokeColor = strokeColor.WithAlpha(0.5f);
} canvas.StrokeColor = strokeColor;
canvas.StrokeSize = strokeSize; canvas.SaveState();
canvas.Translate(path.Pos.X, path.Pos.Y);
canvas.DrawPath(path.Path);
canvas.RestoreState();
} private void DrawEraser(ICanvas canvas)
{
canvas.SaveState();
canvas.Translate(Eraser.Pos.X, Eraser.Pos.Y);
canvas.Scale(0.2f, 0.2f);
canvas.StrokeColor = Eraser.StrokeColor ?? Colors.Black;
canvas.StrokeSize = Eraser.StrokeThickness;
canvas.FillColor = Color.FromArgb("#FFD700");
canvas.FillPath(Eraser.Path);
canvas.DrawPath(Eraser.Path);
canvas.RestoreState();
} private void DrawSelectionRect(ICanvas canvas, RectF rect)
{
canvas.SaveState();
canvas.StrokeColor = SelectionColor;
canvas.StrokeSize = SelectionStrokeThickness;
canvas.StrokeDashPattern = new float[] { 5, 3 };
canvas.DrawRectangle(rect);
canvas.RestoreState();
} // 静态工具方法
public static float Distance(PointF a, PointF b)
=> (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2)); public static float DistanceSquared(PointF a, PointF b)
=> (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y); public static float PointToLineDistance(PointF lineStart, PointF lineEnd, PointF point)
{
float l2 = DistanceSquared(lineStart, lineEnd);
if (l2 == 0) return Distance(point, lineStart); float t = Math.Max(0, Math.Min(1, Vector2.Dot(
new Vector2(point.X - lineStart.X, point.Y - lineStart.Y),
new Vector2(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y)) / l2)); PointF projection = new PointF(
lineStart.X + t * (lineEnd.X - lineStart.X),
lineStart.Y + t * (lineEnd.Y - lineStart.Y)
); return Distance(point, projection);
}
}

SimpleInkCanvas.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"
x:Class="MauiViews.MauiDemos.Book._03.SimpleInkCanvas"
Title="SimpleInkCanvas" HeightRequest="300" WidthRequest="300">
<Grid RowDefinitions="auto,*">
<StackLayout Margin="5" Orientation="Horizontal">
<Label Text="EditingMode:" Margin="5" VerticalOptions="Center" FontSize="16"/>
<Picker x:Name="lstEditingMode" VerticalOptions="Center"/>
</StackLayout>
<local:InkCanvas Grid.Row="1" BackgroundColor="LightYellow"
EditingMode="{Binding Path=SelectedIndex,
Source={x:Reference lstEditingMode}, Converter={StaticResource Converter}}"/>
<Button Text="Hello" Grid.Row="1" WidthRequest="78" HeightRequest="16"
HorizontalOptions="Start" VerticalOptions="Start"/>
</Grid>
</ContentPage>

对应的cs代码

using static Shares.Utility.InkCanvas;

namespace MauiViews.MauiDemos.Book._03;

public partial class SimpleInkCanvas : ContentPage
{
public SimpleInkCanvas()
{
InitializeComponent();
foreach (InkCanvasEditingMode mode in Enum.GetValues(typeof(InkCanvasEditingMode)))
{
lstEditingMode.Items.Add(mode.ToString());
lstEditingMode.SelectedIndex = 0;
}
}
}

运行效果

03 - LayoutPanels例子 - SimpleInkCanvas的更多相关文章

  1. day23 03 组合的例子

    day23 03 组合的例子 一.用到组合的方式,编写一个圆环,并能够计算出它的周长和面积 from math import pi # 从内置函数里面导入pi # 先定义一个圆类 class Circ ...

  2. python 各模块

    01 关于本书 02 代码约定 03 关于例子 04 如何联系我们 1 核心模块 11 介绍 111 内建函数和异常 112 操作系统接口模块 113 类型支持模块 114 正则表达式 115 语言支 ...

  3. c socket(续)

    存在两种字节顺序:NBO与HBO 网络字节顺序NBO(Network Byte Order):按从高到低的顺序存储,在网络上使用统一的网络字节顺序,可以避免兼容性问题. 主机字节顺序(HBO,Host ...

  4. 编程那些事儿:如何快速地"借用"CSS

    做前端开发有时候会碰到任务紧急,需要马上写好静态页的问题.比如,设计师给你扔了一个设计稿,要你在下班之前搞定.这时候你如热锅上的蚂蚁,如果自己写css的话,时间紧张,于是上网找了一下相关模板页面,找到 ...

  5. urls.py的配置[路由配置]

    urls.py的配置[路由配置] Get请求与Post请求的方式 get请求: (1)地址栏输入url (2)<a href="请求url">点击</a> ...

  6. 【Python】【自动化测试】【pytest】

    https://docs.pytest.org/en/latest/getting-started.html#create-your-first-test http://www.testclass.n ...

  7. c# 主机和网络字节序的转换 关于网络字节序和主机字节序的转换

    最近使用C#进行网络开发,需要处理ISO8583报文,由于其中有些域是数值型的,于是在传输的时候涉及到了字节序的转换. 字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有两种字节顺序, ...

  8. [C++][转]CPU字节序 网络序 主机序 大端小端

    原帖:http://www.cnblogs.com/darktime/p/3298075.html 不同的CPU有不同的字节序类型 这些字节序是指整数在内存中保存的顺序 这个叫做主机序最常见的有两种1 ...

  9. Python:数字的格式化输出

    >>> 'The value is {:0,.2f}'.format(x) 'The value is 1,234.57' 需要将数字格式化后输出,并控制数字的位数.对齐.千位分隔符 ...

  10. [Python3] 027 常用模块 time

    目录 time 1. 时间戳 2. UTC 时间 3. 夏令时 4. 时间元组 5. 举例 5.1 例子1 例子2 例子3 例子4 例子5 例子6 例子7 time 1. 时间戳 一个时间表示,根据不 ...

随机推荐

  1. 插入排序(LOW)

    博客地址:https://www.cnblogs.com/zylyehuo/ # _*_coding:utf-8_*_ def insert_sort(li): for i in range(1, l ...

  2. JAVA调用Python脚本执行

    SpringBoot-web环境 <dependency> <groupId>org.springframework.boot</groupId> <arti ...

  3. css标签名

    标签 功能介绍 p 标签选择器 #id id选择器 .class class选择器 * 通用选择器 p,a 多个标签选择器 p a 后代选择器 p>a 子代选择器 p+a 紧挨着p后面的a选择器 ...

  4. 痞子衡嵌入式:恩智浦i.MX RT1xxx上特色外设XBAR那些事(1)- 初识

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是恩智浦i.MX RT1xxx系列上的XBAR外设. 得益于 Arm Cortex-M 内核的普及,现如今 MCU 厂商遍地开花,只要能取 ...

  5. DevOps与:cloud,IaC,Container,Microservices, Serverless

    本文我们来理一理当下最火的技术与DevOps的关系,2020年可以考虑从如下的几方面来改进DevOps. DevOps代表开发和运营.它的目标是将开发.质量保证和运营(部署和集成)合并到一个单一的.连 ...

  6. openGL库环境简单配置

    主要是针对openGL的一些初步的学习,因为openCV主要是处理图像视频,是从现有的得到数据,而openGL好像是从数据进行绘图,学习一下.在<计算机图形学编程>一书中,它把图形编程定性 ...

  7. CH9121 FTP使用详解

    一.FTP简介: FTP是基于TCP应用层的网络文件传输协议,支持两种模式:Standard (PORT方式,主动方式),Passive (PASV,被动方式).采用明文通信不加密. 1.Port模式 ...

  8. 工会成立100周年纪念,开发职工健身AI运动小程序、APP方案推荐

    时光荏苒,转眼间2025年五一将至,这一年对于中华全国总工会而言,具有非凡的历史意义--它将迎来成立100周年的辉煌时刻.为了庆祝这一盛事,各级工会组织将精心筹备了一系列丰富多彩.形式多样的纪念活动, ...

  9. oracle的order by 中文排序原理

    近期发现oracle的order by中文排序并不是完全按照拼音排序的 经过测试发现oracle的order by中文排序是按照中文的ASCII码排序的 查询字符ASCII码 select ascii ...

  10. Asp.net mvc基础(十)判断是否是ajax请求

    通过Request.IsAjaxRequest()方法进行判断是否是ajax的请求,true是ajax的请求,false不是ajax的请求 后端: 前端: 效果: 使用Request.IsAjaxRe ...