我们提供安全,免费的手游软件下载!
自定义控件是我比较陌生的一个主题。我好久没练习过wpf了,需要巩固记忆。我想了一会儿,打开动漫之家,忽然觉得这个看漫画的图片浏览控件有意思。于是特地花了一天做了这个图片控件。我原本以为很容易,但实际上并不简单。这个图片浏览控件比我想象中要难许多,有技术上的难题,也有逻辑上的难题。好在最后都解决了。
这个自定义控件算是比较好的练习。里面涉及了
依赖属性
Binding
虚拟化加载
VisualState
动态资源
过渡效果
部件
光标
这些东西。
对于电脑端来说,一个好看的光标是必不可少的。于是我查询了资料,了解到一种比较简洁的自定义光标方式。就是制作光标文件,然后在 XAML 中直接使用。
到哪里找图标呢呢?我想起了 FontAwsome.V5 ,这个非常方便,并且美观。于是我到官网上找到了两个箭头图标。下一步是把图标转化成光标文件。 FontAwsome.V5 提供了svg格式的图标复制,然后我在这个网站发现了 -->在线转换器<-- 可以将svg转化为cur光标文件,它提供了每天10次的免费额度。但要注意,需要设定光标尺寸。可以设置另存为文件之后,把svg元素的 Width 和 Height 属性改为16。
最后把光标文件放到项目中,以生成 资源 的形式编译。这里就能在XAML中直接使用了。如前面所示,是一个黑色箭头。
作为一个图片浏览控件,控制面板必不可少。比较经典的做法是
点击中间显示面板帮助。点击两边切换图片
。面板帮助我就暂且省略,就做了切换图片的功能。整个自定义控件的是一个
Grid
,分为2列,然后两个按钮各占1列,再把按钮隐藏起来。幸好,透明度为0时还能捕获事件。下方再重叠几个宽度为2列的
Image
控件作为视口。
经过多次更改,最终我把这个自定义控件设计为了一个分为4层 Z-Index 的控件。
这是我为了实现正常显示效果,再调试过程中一层层加上去的。但是XAML中没法给非
Canvas
子控件设置这个
附加属性
,所以这个附加属性只能在逻辑控件的代码中进行动态设置。这些设置代码与控件交互逻辑交织,本身就是控制逻辑的一部分。
var P_Bleft = (Button)GetTemplateChild("P_Bleft");
P_Bleft.SetValue(Canvas.ZIndexProperty, 4);
P_Bright.SetValue(Canvas.ZIndexProperty, 4);
P_Bulkhead.SetValue(Canvas.ZIndexProperty, 1);
作为一个图片浏览器,把全部图片一次性加载出来是一个方便的选择,但不是一个明智的选择。所以我考虑了
虚拟化加载
。最开始我想到了集合控件
ListView
。但是这个我需要过渡效果。集合控件似乎没有实现这个。并且对内容大小控制也不是很完美。我还是自己从0开始做。
我走了很多弯路,最后确定使用3个水平排列的
Image
控件进行图片的呈现。使用
Storyboard
时间线控制过渡效果。我把图片URL集合组织成一个环形,然后让这3个
Image
在环上移动,这样实现虚拟化加载。
首先是预加载当前页和下一页。点击下一页之后,图片控件向左移动,同时最左边的、移动到视口之外的 Image 控件加载下下一页。第二次点击时,把首次预加载的图片移动到视口内,再把第二次预加载的图片移动到下一页位置。第三次点击时,这个控件移动到视口内,每个控件都有类似的过程,循环往复。同时还要考虑方向,即点击上一页。所以这要在逻辑代码中判断处理。
我用简单的 取模运算符 将数据组织成环形
//当前页
int state = (Current) % 3;
然后在一个
switch
结构中完成控制逻辑
switch (state)
{
case 0:
//很多判断
break;
case 1:
//很多判断
break;
case 2:
//很多判断
break;
}
在 swich 结构中的几个重要工作是
RenderTransform
。位置一个有3个
-width、0、width
。每个控件在每个时刻,个占据一个位置
//左移
P_Cimage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
//右移
if(Current + 1 < Urls.Count)
{
P_Cimage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
}
使用
VisualState
可以简化这种管理。我把这种切换设计为3个状态
Normal
R_Translate
L_Translate
。在不同状态时,3个图片控件分别占据不同位置,然后根据数据组织的环形结构进行状态切换。
这里比较麻烦的地方在于,状态中的动画没法没法绑定到依赖属性,其次也没法用 动态资源 进行切换属性值。还是得到逻辑代码中进行控制。
P_BL.RenderTransform.Value.Translate(-P_BC.ActualWidth, 0);
P_BC.RenderTransform.Value.Translate(0, 0);
P_BR.RenderTransform.Value.Translate(P_BC.ActualWidth, 0);
d4_.To = -P_BC.ActualWidth;
d5_.To = 0;
d6_.To = P_BC.ActualWidth;
d4.From = 0;
d5.From = P_BC.ActualWidth;
d6.From = -P_BC.ActualWidth;
为了响应控件大小变化后,动画元素还能够精确到达所需偏移位置,就要每次点击前
微调
控件位置。要让
Image
元素的
RenderTransform
使用
DynamicResource
,才能在代码中进行更新位置。全屏后点击下一页,下一页
Image
控件的偏移量是
1920
,但求出全屏后,需要的偏移量就小多了,需要根据控件大小动态计算。主要原因是因为这里要使用动画,不然的话,使用布局计算可能是是一个比较好的方式。
-1200
1200
0:0:0.2
namespace Pictures
{
[TemplatePart(Name = "P_Bleft",Type = typeof(Button))]
[TemplatePart(Name = "P_Bright", Type = typeof(Button))]
[TemplatePart(Name = "P_Limage", Type = typeof(Image))]
[TemplatePart(Name = "P_Cimage", Type = typeof(Image))]
[TemplatePart(Name = "P_Rimage", Type = typeof(Image))]
[TemplatePart(Name = "P_BC", Type = typeof(Border))]
[TemplatePart(Name = "P_BL", Type = typeof(Border))]
[TemplatePart(Name = "P_BR", Type = typeof(Border))]
[TemplatePart(Name = "P_Bulkhead",Type =typeof(Border))]
[TemplatePart(Name = "P_BC", Type = typeof(Border))]
[TemplatePart(Name = "P_BL", Type = typeof(Border))]
[TemplatePart(Name = "P_BR", Type = typeof(Border))]
[TemplateVisualState(GroupName = "group1",Name = "L_Translate")]
[TemplateVisualState(GroupName = "group1", Name = "R_Translate")]
[TemplateVisualState(GroupName = "group1", Name = "Normal")]
[TemplatePart(Name = "d1", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d2", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d3", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d4", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d5", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d6", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d7", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d8", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d9", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d1_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d2_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d3_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d4_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d5_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d6_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d7_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d8_", Type = typeof(DoubleAnimation))]
[TemplatePart(Name = "d9_", Type = typeof(DoubleAnimation))]
public class CustomControl2 : Control
{
public const string L_Translate = "L_Translate";
public const string R_Translate = "R_Translate";
public const string Normal = "Normal";
static CustomControl2()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl2), new FrameworkPropertyMetadata(typeof(CustomControl2)));
}
///
/// 当前页
///
public int Current
{
get { return (int)GetValue(CurrentProperty); }
set { SetValue(CurrentProperty, value); }
}
public static readonly DependencyProperty CurrentProperty =
DependencyProperty.Register("Current", typeof(int), typeof(CustomControl2), new PropertyMetadata(0));
///
/// 图片
///
public ObservableCollection Urls
{
get { return (ObservableCollection)GetValue(UrlsProperty); }
set { SetValue(UrlsProperty, value); }
}
public static readonly DependencyProperty UrlsProperty =
DependencyProperty.Register("Urls", typeof(ObservableCollection), typeof(CustomControl2), new PropertyMetadata(null, OnUrlsChanged));
private static void OnUrlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomControl2 control = d as CustomControl2;
if (control != null)
{
control.OnUrlsChanged((ObservableCollection)e.OldValue, (ObservableCollection)e.NewValue);
}
}
private void OnUrlsChanged(ObservableCollection oldValue, ObservableCollection newValue)
{
Current = 0;
BeginStoryBoard(0, true);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Button P_Bleft = (Button)GetTemplateChild("P_Bleft");
Button P_Bright = (Button)GetTemplateChild("P_Bright");
//隔板
Border P_Bulkhead = (Border)GetTemplateChild("P_Bulkhead");
P_Bleft.SetValue(Canvas.ZIndexProperty, 4);
P_Bright.SetValue(Canvas.ZIndexProperty, 4);
P_Bulkhead.SetValue(Canvas.ZIndexProperty, 1);
P_Bleft.Click += (s, e) => ToPage(-1);
P_Bright.Click += (s, e) => ToPage(1);
}
private void ToPage(int offset)
{
if (Current==0 && offset<0)
{
return;
}
if (Current>=Urls.Count-1 && offset>0)
{
return;
}
Current += offset;
BeginStoryBoard(offset);
}
private void BeginStoryBoard(int offset,bool init=false)
{
Image P_Cimage = (Image)GetTemplateChild("P_Cimage");
Image P_Limage = (Image)GetTemplateChild("P_Limage");
Image P_Rimage = (Image)GetTemplateChild("P_Rimage");
Border P_BC = (Border)GetTemplateChild("P_BC");
Border P_BL = (Border)GetTemplateChild("P_BL");
Border P_BR = (Border)GetTemplateChild("P_BR");
Application.Current.Resources["left"]= -P_BC.ActualWidth;
Application.Current.Resources["right"] = P_BC.ActualWidth;
//当前页
int state = (Current) % 3;
//由于动画不能绑定To属性,只能在代码中动态调整
#region 调整状态
DoubleAnimation d1 = (DoubleAnimation)GetTemplateChild("d1");
DoubleAnimation d2 = (DoubleAnimation)GetTemplateChild("d2");
DoubleAnimation d3 = (DoubleAnimation)GetTemplateChild("d3");
DoubleAnimation d4 = (DoubleAnimation)GetTemplateChild("d4");
DoubleAnimation d5 = (DoubleAnimation)GetTemplateChild("d5");
DoubleAnimation d6 = (DoubleAnimation)GetTemplateChild("d6");
DoubleAnimation d7 = (DoubleAnimation)GetTemplateChild("d7");
DoubleAnimation d8 = (DoubleAnimation)GetTemplateChild("d8");
DoubleAnimation d9 = (DoubleAnimation)GetTemplateChild("d9");
DoubleAnimation d1_ = (DoubleAnimation)GetTemplateChild("d1_");
DoubleAnimation d2_ = (DoubleAnimation)GetTemplateChild("d2_");
DoubleAnimation d3_ = (DoubleAnimation)GetTemplateChild("d3_");
DoubleAnimation d4_ = (DoubleAnimation)GetTemplateChild("d4_");
DoubleAnimation d5_ = (DoubleAnimation)GetTemplateChild("d5_");
DoubleAnimation d6_ = (DoubleAnimation)GetTemplateChild("d6_");
DoubleAnimation d7_ = (DoubleAnimation)GetTemplateChild("d7_");
DoubleAnimation d8_ = (DoubleAnimation)GetTemplateChild("d8_");
DoubleAnimation d9_ = (DoubleAnimation)GetTemplateChild("d9_");
d1.To = -P_BC.ActualWidth;
d2.To = 0;
d3.To = P_BC.ActualWidth;
d4.To = -P_BC.ActualWidth;
d5.To = 0;
d6.To = P_BC.ActualWidth;
d7.To = -P_BC.ActualWidth;
d8.To = 0;
d9.To = P_BC.ActualWidth;
d1_.To = -P_BC.ActualWidth;
d2_.To = 0;
d3_.To = P_BC.ActualWidth;
d4_.To = -P_BC.ActualWidth;
d5_.To = 0;
d6_.To = P_BC.ActualWidth;
d7_.To = -P_BC.ActualWidth;
d8_.To = 0;
d9_.To = P_BC.ActualWidth;
#endregion
switch (state)
{
case 0:
P_BC.SetValue(Canvas.ZIndexProperty, 3);
if (init)
{
if (Urls.Count>0)
{
P_Cimage.Source = new BitmapImage(new Uri(Urls[0]));
}
if (Urls.Count > 1)
{
P_Rimage.Source = new BitmapImage(new Uri(Urls[1]));
}
P_BL.RenderTransform.Value.Translate(-P_BC.ActualWidth, 0);
P_BC.RenderTransform.Value.Translate(0, 0);
P_BR.RenderTransform.Value.Translate(P_BC.ActualWidth, 0);
}
else
{
if (offset==1)
{
//右移
if(Current + 1 < Urls.Count)
{
P_Rimage.Source = new BitmapImage(new Uri(Urls[Current + 1]));
}
P_BL.SetValue(Canvas.ZIndexProperty, 2);
P_BR.SetValue(Canvas.ZIndexProperty, 0);
d1.From = 0;
d2.From = P_BC.ActualWidth;
d3.From = -P_BC.ActualWidth;
}
if (offset==-1)
{
//左移
if (Current>0)
{
P_Limage.Source = new BitmapImage(new Uri(Urls[Current - 1]));
}
P_BL.SetValue(Canvas.ZIndexProperty, 0);
P_BR.SetValue(Canvas.ZIndexProperty, 2);
d1.From = P_BC.ActualWidth;
d2.From = -P_BC.ActualWidth;
d3.From = 0;
}
}
VisualStateManager.GoToState(this, Normal, true);
break;
case 1:
P_BR.SetValue(Canvas.ZIndexProperty, 3);
if (offset == 1)
{
//右移
if (Current+1
热门资讯