WPF 实现用户头像选择器 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
yanjinhua
V2EX    C#

WPF 实现用户头像选择器

  •  
  •   yanjinhua 2022 年 7 月 26 日 2098 次点击

    制作一个用户头像选择器仿 WeGame

    • 制作一个用户头像选择Canvas为父控件所实现,展示图片使用ImagePath当作上方的蒙版;
    • Canvas:主要用途方便移动Image,设置ClipToBounds="True"裁剪为一个正方形200x200做为主要展示区域;
    • Image:展示需要裁剪的图片;

    • 当选择一个本地图片的时候判断宽与高谁更大,谁小就将它更改为200 ,另一边做等比缩放后给到DrawingVisual绘制一个新的BitmapFrameImage控件做展示;
    • 当移动图片的时候右侧展示当前区域使用CroppedBitmap进行裁剪并显示;
    • 源码Github Gitee

    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; } } } } 

    [1] Github: https://github.com/WPFDevelopersOrg/WPFDevelopers

    [2] Gitee: https://gitee.com/WPFDevelopersOrg/WPFDevelopers

    2 条回复    2022-07-26 13:13:21 +08:00
    Eiden
        1
    Eiden  
       2022 年 7 月 26 日
    加个缩放就好了
    yanjinhua
        2
    yanjinhua  
    OP
       2022 年 7 月 26 日
    @Eiden 感谢评论,目前没有缩放,以后增加。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4448 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 31ms UTC 08:14 PVG 16:14 LAX 00:14 JFK 03:14
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86