当前位置:网站首页>WPF 实现用户头像选择器
WPF 实现用户头像选择器
2022-07-25 17:32:00 【yanjinhua】
制作一个用户头像选择器仿 WeGame
制作一个用户头像选择 Canvas为父控件所实现,展示图片使用Image,Path当作上方的蒙版;Canvas:主要用途方便移动Image,设置ClipToBounds="True"裁剪为一个正方形200x200做为主要展示区域;Image:展示需要裁剪的图片;

Path: CombinedGeometry[1]绘制蒙版大小200x200效果如下;

当选择一个本地图片的时候判断宽与高谁更大,谁小就将它更改为 200,另一边做等比缩放后给到DrawingVisual绘制一个新的 BitmapFrame[2]给Image控件做展示;当移动图片的时候右侧展示当前区域使用 CroppedBitmap[3]进行裁剪并显示; 源码 Github[4]Gitee[5]
1)CropAvatar.xaml 代码如下;
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFDevelopers.Controls">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Basic/ControlBasic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="controls:CropAvatar" BasedOn="{StaticResource ControlBasicStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:CropAvatar}">
<Canvas x:Name="PART_Canvas" ClipToBounds="True">
<Image x:Name="PART_Image" Cursor="SizeAll" ></Image>
<Path x:Name="PART_Layout"
Fill="{DynamicResource BlackSolidColorBrush}"
Width="200" Height="200"
Opacity=".5">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Xor">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0,200,200"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="100,100" RadiusX="100" RadiusY="100"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
<Grid x:Name="PART_Grid" Width="200" Height="200">
<Button x:Name="PART_ReplaceButton" Style="{StaticResource PathButton}"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="40" Height="40" ToolTip="更换图片"
Visibility="Collapsed">
<Button.Content>
<Path Data="{StaticResource PathReplace}"
Fill="{StaticResource PrimaryNormalSolidColorBrush}"
Height="15"
Width="15"
Stretch="Fill" />
</Button.Content>
</Button>
<Button x:Name="PART_AddButton" Style="{StaticResource PathButton}"
Width="40" Height="40" ToolTip="选择图片">
<Button.Content>
<Path Data="{StaticResource PathAdd}"
Fill="{StaticResource PrimaryNormalSolidColorBrush}"
Height="20"
Width="20"
Stretch="Fill"
RenderTransformOrigin="0.5,0.5" IsHitTestVisible="False">
<Path.RenderTransform>
<RotateTransform Angle="45"/>
</Path.RenderTransform>
</Path>
</Button.Content>
</Button>
</Grid>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
2)CropAvatar.cs 代码如下;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using WPFDevelopers.Helpers;
namespace WPFDevelopers.Controls
{
[TemplatePart(Name = CanvasTemplateName, Type = typeof(Canvas))]
[TemplatePart(Name = ImageTemplateName, Type = typeof(Image))]
[TemplatePart(Name = PathTemplateName, Type = typeof(Path))]
[TemplatePart(Name = GridTemplateName, Type = typeof(Grid))]
[TemplatePart(Name = ReplaceButtonTemplateName, Type = typeof(Button))]
[TemplatePart(Name = AddButtonTemplateName, Type = typeof(Button))]
public partial class CropAvatar : Control
{
private const string CanvasTemplateName = "PART_Canvas";
private const string ImageTemplateName = "PART_Image";
private const string PathTemplateName = "PART_Layout";
private const string GridTemplateName = "PART_Grid";
private const string ReplaceButtonTemplateName = "PART_ReplaceButton";
private const string AddButtonTemplateName = "PART_AddButton";
private Point point;
private const int _size = 200;
private bool isDown;
private bool isLeft;
private CroppedBitmap crop;
private Canvas canvas;
private Image image;
private Path path;
private Grid grid;
private Button replaceButton, addButton;
private int initialX, initialY, voffsetX, voffsetY;
private double vNewStartX, vNewStartY, _StartX, _StartY, centerX, centerY;
private BitmapFrame bitmapFrame;
public ImageSource OutImageSource
{
get { return (ImageSource)GetValue(OutImageSourceProperty); }
set { SetValue(OutImageSourceProperty, value); }
}
public static readonly DependencyProperty OutImageSourceProperty =
DependencyProperty.Register("OutImageSource", typeof(ImageSource), typeof(CropAvatar), new PropertyMetadata(null));
static CropAvatar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CropAvatar), new FrameworkPropertyMetadata(typeof(CropAvatar)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
canvas = GetTemplateChild(CanvasTemplateName) as Canvas;
canvas.Loaded += Canvas_Loaded;
grid = GetTemplateChild(GridTemplateName) as Grid;
image = GetTemplateChild(ImageTemplateName) as Image;
image.MouseDown += Image_MouseDown;
image.MouseMove += Image_MouseMove;
image.MouseUp += Image_MouseUp;
image.MouseLeave += Image_MouseLeave;
path = GetTemplateChild(PathTemplateName) as Path;
replaceButton = GetTemplateChild(ReplaceButtonTemplateName) as Button;
replaceButton.Click += ReplaceButton_Click;
addButton = GetTemplateChild(AddButtonTemplateName) as Button;
addButton.Click += AddButton_Click;
}
private void Canvas_Loaded(object sender, RoutedEventArgs e)
{
if (sender is Canvas canvas)
{
var width = canvas.ActualWidth;
var height = canvas.ActualHeight;
centerX = (width - path.Width) / 2.0d;
centerY = (height - path.Height) / 2.0d;
canvas.Clip = new RectangleGeometry(new Rect(centerX, centerY, 200, 200));
Canvas.SetLeft(path, centerX);
Canvas.SetTop(path, centerY);
Canvas.SetLeft(grid, centerX);
Canvas.SetTop(grid, centerY);
}
}
private void Image_MouseLeave(object sender, MouseEventArgs e)
{
isDown = false;
if (isLeft)
_StartX = Canvas.GetLeft(image);
else
_StartY = Canvas.GetTop(image);
}
private void Image_MouseUp(object sender, MouseButtonEventArgs e)
{
if (isDown)
{
var vPoint = e.GetPosition(this);
if (isLeft)
{
_StartX = Canvas.GetLeft(image);
initialX = voffsetX;
}
else
{
_StartY = Canvas.GetTop(image);
initialY = voffsetY;
}
}
}
private void Image_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed && isDown)
{
var vPoint = e.GetPosition(this);
if (isLeft)
{
var voffset = vPoint.X - point.X;
vNewStartX = _StartX + voffset;
var xPath = Canvas.GetLeft(path);
if (vNewStartX <= xPath && vNewStartX >= -(bitmapFrame.Width - 200 - xPath))
{
Canvas.SetLeft(image, vNewStartX);
voffsetX = initialX - (int)voffset;
voffsetX = voffsetX < 0 ? 0 : voffsetX;
crop = new CroppedBitmap(bitmapFrame, new Int32Rect(voffsetX, 0, _size, _size));
}
}
else
{
var voffset = vPoint.Y - point.Y;
vNewStartY = _StartY + voffset;
var yPath = Canvas.GetTop(path);
if (vNewStartY <= yPath && vNewStartY >= -(bitmapFrame.Height - 200 - yPath))
{
Canvas.SetTop(image, vNewStartY);
voffsetY = initialY - (int)voffset;
voffsetY = voffsetY < 0 ? 0 : voffsetY;
crop = new CroppedBitmap(bitmapFrame, new Int32Rect(0, voffsetY, _size, _size));
}
}
OutImageSource = crop;
}
}
private void Image_MouseDown(object sender, MouseButtonEventArgs e)
{
isDown = true;
point = e.GetPosition(this);
}
private void ReplaceButton_Click(object sender, RoutedEventArgs e)
{
InitialImage();
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
InitialImage();
}
void InitialImage()
{
vNewStartX = 0;
vNewStartY = 0;
var uri = ControlsHelper.ImageUri();
if (uri == null) return;
var bitmap = new BitmapImage(uri);
if (bitmap.Height > bitmap.Width)
{
double scale = (double)bitmap.Width / (double)path.Width;
image.Width = _size;
image.Height = (double)bitmap.Height / scale;
isLeft = false;
}
else if (bitmap.Width > bitmap.Height)
{
double scale = (double)bitmap.Height / (double)path.Height;
image.Width = (double)bitmap.Width / scale;
image.Height = _size;
isLeft = true;
}
bitmapFrame = ControlsHelper.CreateResizedImage(bitmap, (int)image.Width, (int)image.Height, 0);
image.Source = bitmapFrame;
if (image.Source != null)
{
replaceButton.Visibility = Visibility.Visible;
addButton.Visibility = Visibility.Collapsed;
}
Canvas.SetLeft(grid, centerX);
Canvas.SetTop(grid, centerY);
_StartX = (canvas.ActualWidth - image.Width) / 2.0d;
_StartY = (canvas.ActualHeight - image.Height) / 2.0d;
Canvas.SetLeft(image, _StartX);
Canvas.SetTop(image, _StartY);
if (isLeft)
{
initialX = (int)(image.Width - 200) / 2;
initialY = 0;
crop = new CroppedBitmap(bitmapFrame, new Int32Rect(initialX, 0, _size, _size));
}
else
{
initialY = (int)(image.Height - 200) / 2;
initialX = 0;
crop = new CroppedBitmap(bitmapFrame, new Int32Rect(0, initialY, _size, _size));
}
OutImageSource = crop;
}
}
}
3)CropAvatarWindow.xaml使用如下;
<ws:Window x:Class="WPFDevelopers.Samples.ExampleViews.CropAvatarWindow"
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:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
xmlns:ws="https://github.com/WPFDevelopersOrg.WPFDevelopers.Minimal"
mc:Ignorable="d" WindowStyle="ToolWindow" ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
Title="WPF 开发者-头像选择器" Height="450" Width="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<wpfdev:CropAvatar x:Name="MyCropAvatar"/>
<Image Grid.Column="1" Name="CropAvatarImage" Source="{Binding ElementName=MyCropAvatar,Path=OutImageSource}"
Stretch="Fill" Width="200" Height="200">
<Image.Clip>
<EllipseGeometry Center="100,100" RadiusX="100" RadiusY="100"/>
</Image.Clip>
</Image>
<UniformGrid Grid.Row="1" Grid.ColumnSpan="2"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Button Content="保存" Click="btnSave_Click" Style="{StaticResource PrimaryButton}" Margin="4,0"/>
<Button Content="关闭" Click="btnClose_Click" Margin="4,0"/>
</UniformGrid>
</Grid>
</ws:Window>
4) CropAvatarWindow.xaml.cs 代码如下;
using System.Windows;
namespace WPFDevelopers.Samples.ExampleViews
{
/// <summary>
/// CropAvatarWindow.xaml 的交互逻辑
/// </summary>
public partial class CropAvatarWindow
{
public CropAvatarWindow()
{
InitializeComponent();
}
private void btnSave_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
}
private void btnClose_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
}
}
}
5) CropAvatarExample.xaml 使用如下;
<UserControl x:Class="WPFDevelopers.Samples.ExampleViews.CropAvatarExample"
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:wpfdev="https://github.com/WPFDevelopersOrg/WPFDevelopers"
xmlns:local="clr-namespace:WPFDevelopers.Samples.ExampleViews"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="图像选择器" VerticalAlignment="Center" HorizontalAlignment="Center" Click="Button_Click"/>
<Image Grid.Column="1" Name="MyImage"
Stretch="Fill" Width="200" Height="200">
<Image.Clip>
<EllipseGeometry Center="100,100" RadiusX="100" RadiusY="100"/>
</Image.Clip>
</Image>
</Grid>
</UserControl>
6) CropAvatarExample.xaml.cs 代码如下;
using System.Windows.Controls;
namespace WPFDevelopers.Samples.ExampleViews
{
/// <summary>
/// CropAvatarExample.xaml 的交互逻辑
/// </summary>
public partial class CropAvatarExample : UserControl
{
public CropAvatarExample()
{
InitializeComponent();
}
private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
var cropAvatarWindow = new CropAvatarWindow();
if (cropAvatarWindow.ShowDialog() == true)
{
MyImage.Source = cropAvatarWindow.CropAvatarImage.Source;
}
}
}
}
参考资料
CombinedGeometry: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.combinedgeometry?view=netframework-4.0
[2]BitmapFrame: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.imaging.bitmapframe?view=windowsdesktop-6.0
[3]CroppedBitmap: https://docs.microsoft.com/zh-cn/dotnet/api/system.windows.media.imaging.croppedbitmap?view=windowsdesktop-6.0
[4]Github: https://github.com/WPFDevelopersOrg/WPFDevelopers
[5]Gitee: https://gitee.com/WPFDevelopersOrg/WPFDevelopers
边栏推荐
- 吴恩达机器学习编程作业无法暂停pause问题解决
- With 8 years of product experience, I have summarized these practical experience of continuous and efficient research and development
- 栈的顺序存储结构,链式存储结构及实现
- From digitalization to intelligent operation and maintenance: what are the values and challenges?
- [Hardware Engineer] Why do DC-DC isolated switching power modules use transformers?
- Pymongo saves data in dataframe format (insert_one, insert_many, multi-threaded saving)
- EasyUI drop-down box, add and put on and off shelves of products
- Wu Enda logistic regression 2
- 我也是醉了,Eureka 延迟注册还有这个坑!
- 对灰度图像的三维函数显示
猜你喜欢

Hcip notes 12 days

Wu Enda logistic regression 2

虚拟内存管理

An article about ultrasonic humidifier
Go language series: where does go come from and where will go?

"Digital security" alert NFT's seven Scams
![[Nanjing University of Aeronautics and Astronautics] information sharing for the first and second examinations of postgraduate entrance examination](/img/d8/a367c26b51d9dbaf53bf4fe2a13917.png)
[Nanjing University of Aeronautics and Astronautics] information sharing for the first and second examinations of postgraduate entrance examination

Jenkins' role based authorization strategy installation configuration
![[solution] the Microsoft edge browser has the problem of](/img/47/7e20a4f1e04577153e7cf0a6c61f26.png)
[solution] the Microsoft edge browser has the problem of "unable to access this page"

世界各地的标志性建筑物
随机推荐
枚举类和魔术值
【硬件工程师】关于信号电平驱动能力
Customize MVC project login registration and tree menu
【解决方案】Microsoft Edge 浏览器 出现“无法访问该页面”问题
[PHP pseudo protocol] source code reading, file reading and writing, and arbitrary PHP command execution
函数名指针和函数指针
约瑟夫环问题
Tkinter module advanced operations (I) -- transparent buttons, transparent text boxes, custom buttons and custom text boxes
用秩讨论线性方程组的解/三个平面的位置关系
更新|3DCAT实时云渲染 v2.1.2版本全新发布
对灰度图像的三维函数显示
[cadence Allegro PCB design] error: possible pin type conflict gnd/vcc power connected to output
Crawler framework crawler
Starting from business needs, open the road of efficient IDC operation and maintenance
吴恩达机器学习编程作业无法暂停pause问题解决
11、照相机与透镜
我也是醉了,Eureka 延迟注册还有这个坑!
动态规划题目记录
Summary of knowledge points for final review of server-side architecture design
Add batch delete