如果你敲了上一篇的代码,经过上一篇各种问题的蹂躏,我相信自定义控件基础部分其实已经了解的七七八八了。那么我们开始进阶,现在这篇讲的才是真正会用到的核心的东西。简化你的代码。给你提供更多的可能,掌握了这篇,才能发挥出来WPF的威力。这一篇学完我们的鸟枪就要换大炮了。

ColorPicker例子分离了行为和可视化外观,其他人员可以动态改变外观的模板。因为不涉及到状态,所以来说相对简单。现在我们来基于现在的内容深入一个难的。

首先创建通过自定义FlipContentPanel控件学习自定义控件下VisualStateManager的使用,一个同类型的数据,我们给2个展示状态,一个是简易的页面、另外一个是详细的页面,我们通过一个状态切换这2种不同UI的呈现效果,状态切换时播放一个过渡动画。这一篇主要是基于上一篇的ColorPicker学习到的自定义控件的知识结合visualStateManager、动画来实现一个基于状态切换的页面。通过状态来管理并控制页面呈现的内容,这样就可以通过状态来实现不同的外观,同时基于自定义控件可以很轻松的实现复杂的效果,并且代码易于维护。

首先,我们创建一个继承自Control的FlipContentPanel自定义无外观控件类。该类包含2个状态:Flipped和Normal。

我们将使用是否是Flipped来控制页面切换2个不同的呈现内容。

重复使用propdp=>2次tab创建我们需要的3个依赖项属性DetailsContent、OverviewContent、IsFlipped,代码如下:

 public object DetailsContent
{
get { return (object)GetValue(DetailsContentProperty); }
set { SetValue(DetailsContentProperty, value); }
} // Using a DependencyProperty as the backing store for DetailsContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DetailsContentProperty =
DependencyProperty.Register("DetailsContent", typeof(object), typeof(FlipContentPanel));
public object OverviewContent
{
get { return (object)GetValue(OverviewContentProperty); }
set { SetValue(OverviewContentProperty, value); }
} // Using a DependencyProperty as the backing store for OverviewContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OverviewContentProperty =
DependencyProperty.Register("OverviewContent", typeof(object), typeof(FlipContentPanel)); public bool IsFlipped
{
get { return (bool)GetValue(IsFlippedProperty); }
set
{
SetValue(IsFlippedProperty, value);
}
} // Using a DependencyProperty as the backing store for IsFlipped. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsFlippedProperty =
DependencyProperty.Register("IsFlipped", typeof(bool), typeof(FlipContentPanel));

在静态构造函数中给FlipContentPanel.cs添加默认外观。

 static FlipContentPanel()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlipContentPanel), new FrameworkPropertyMetadata(typeof(FlipContentPanel)));
}

我们设计了2个页面,一个DetailsContent页面,一个OverviewContent页面,我们在这2个页面的默认样式中各添加一个按钮,用来控制切换VisualState。在上一篇讲的OnApplyTemplate()中我们从模板中获取控件,然后绑定触发事件。这一篇我们结合模板中的按钮在onApplyTemplate()的过程中查找元素并添加事件,用于控制切换状态,这样内置集成在自定义控件中的好处是如果有元素了就附加事件,如果没有就不附加事件,两个按钮起名为FlipButton和FlipButtonAlternate。

 public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ToggleButton flipButton = base.GetTemplateChild("FlipButton") as ToggleButton;
if (flipButton != null)
{
flipButton.Click += FlipButton_Click;
} ToggleButton flipAlternateButton = base.GetTemplateChild("FlipButtonAlternate") as ToggleButton;
if (flipAlternateButton != null)
{
flipAlternateButton.Click += FlipButton_Click;
}
ChangedVisualState(false);
} private void FlipButton_Click(object sender, RoutedEventArgs e)
{
this.IsFlipped = !this.IsFlipped
} protected void ChangedVisualState(bool value)
{
if (IsFlipped)
{
VisualStateManager.GoToState(this, "Flipped", value);
}
else
{
VisualStateManager.GoToState(this, "Normal", value);
}
}

同时修改依赖项属性IsFlipped的Set方法,当IsFlipped发生改变时,去修改VisualState。(PS:这里经过大佬们的提醒,在这种属性值的Get、Set里尽量不要写内容,容易养成习惯以后给自己留坑,SetValue代码走不到,这部分代码修改如下:)

   public bool IsFlipped
{
get { return (bool)GetValue(IsFlippedProperty); }
set
{
SetValue(IsFlippedProperty, value);
}
}
// Using a DependencyProperty as the backing store for IsFlipped. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsFlippedProperty =
DependencyProperty.Register("IsFlipped", typeof(bool), typeof(FlipContentPanel),new PropertyMetadata(false,IsFlippedChanged)); private static void IsFlippedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FlipContentPanel flipContentPanel)
{
flipContentPanel.ChangedVisualState(true);
}
}

这样加上2个状态,2个按钮在模板中我们就有4个对象。我们使用特性在类上方标明这4个对象。

 	[TemplateVisualState(Name = "Flipped",GroupName = "VisualState")]
[TemplateVisualState(Name ="Normal",GroupName = "VisualState")]
[TemplatePart(Name = "FlipButton", Type = typeof(ToggleButton))]
[TemplatePart(Name = "FlipButtonAlternate", Type = typeof(ToggleButton))]
public class FlipContentPanel : Control

这样我们的FlipContentPanel无外观自定义控件就写完了。他包含了2个状态,会从控件模板中获取2个按钮,并添加事件,用于切换页面。在静态构造函数中还会读取默认外观。

接下来我们去写FlipContentPanel的默认外观。

新建一个资源字典名字叫做FlipContentPanel:

在其中添加Style,TargetType为我们的FlipContentPanel并重写template。

Template中的ContrlTemplate我们在一个Grid中写2个同样大小和位置的Border。这2个border中包含我们用来呈现2个不同页面内容的ContentPresenter。然后通过切换2个Border的Visibility属性来控制2个border的(相当于页面的)显示或隐藏的切换。

对应的ContentPresenter 绑定我们的依赖项属性OverviewContent和DetailsContent。每个ContentPresenter上面还有一个固定位置的ToggleButton,用来切换当前的显示内容,这2个ToggleButton的Name保持和无外观控件中我们定义的FlipButton和FlipButtonAlternate一致,用于在OnApplyTemplate()阶段能给这2个ToggleButton绑定事件,用于切换VisualState。同时我们在ControlTemplate中添加VisualState的管理器。用于切换状态,其实VisualStateManager这里想使用的好也需要单独讲一下,我在这里就被坑了2天。想实现的功能特别牛逼,但是发现这里如果应用不好的话,问题会特别多,实现出来的效果跟预期不一样。以后会单独在Blend下讲这个VisualStateManager。因为他有VisualStateGrounps的概念。可以很好的完成很多种状态之前的切换。这里我们就写一个最简单的切换过程中渐显和渐隐,整体代码如下:

 <Style TargetType="{x:Type local:FlipContentPanel}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlipContentPanel}">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualState">
<VisualStateGroup.Transitions>
<VisualTransition From="Normal" To="Flipped" GeneratedDuration="0:0:0.5">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DetailsContentBorder">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
<VisualTransition From="Flipped" To="Normal" GeneratedDuration="0:0:0.5" >
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DetailsContentBorder">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Flipped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="OverviewContentBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="DetailsContentBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="OverviewContentBorder"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FlipContentPanel},Mode=FindAncestor},Path=ActualHeight}"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FlipContentPanel},Mode=FindAncestor},Path=ActualWidth}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<Grid>
<ContentPresenter Content="{TemplateBinding OverviewContent}"/>
<ToggleButton x:Name="FlipButton" Width="50" Height="50" VerticalAlignment="Bottom" HorizontalAlignment="Left" Content="显示详情"/>
</Grid>
</Border>
<Border x:Name="DetailsContentBorder" BorderBrush="{TemplateBinding BorderBrush}" VerticalAlignment="Bottom" HorizontalAlignment="Left"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FlipContentPanel},Mode=FindAncestor},Path=ActualHeight}"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:FlipContentPanel},Mode=FindAncestor},Path=ActualWidth}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<Grid>
<ContentPresenter Content="{TemplateBinding DetailsContent}"/>
<ToggleButton x:Name="FlipButtonAlternate" Width="50" Height="50" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="30" Content="收起"/>
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

注意上面的代码中From="Normal" To="Flipped" 和From="Flipped" To="Normal"2个VisualTransition。它们是状态切换时执行的。和 是状态切换后执行的。所以我设置了不同的动画。这里一定要多练以下。这里整体就这么多,配个图把,这里卡了我3个晚上。

XAML使用的代码如下,然后配个图:

<Window x:Class="CustomElement.MainWindow"
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:CustomElement"
xmlns:usercontrols="clr-namespace:CustomElement.UserControls"
xmlns:lib="clr-namespace:CustomControls;assembly=CustomControls"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Background="Firebrick">
<Grid.RowDefinitions>
<RowDefinition Height="40"/>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<lib:FlipContentPanel x:Name="flipPanel" Background="AntiqueWhite" Grid.Row="1" BorderBrush="DarkBlue" BorderThickness="3" IsFlipped="false">
<lib:FlipContentPanel.DetailsContent>
<Grid>
<TextBlock Background="Red" Text="我是详情页"/>
</Grid>
</lib:FlipContentPanel.DetailsContent>
<lib:FlipContentPanel.OverviewContent>
<Grid>
<TextBlock Background="Yellow" Text="我是列表页"/>
</Grid>
</lib:FlipContentPanel.OverviewContent>
</lib:FlipContentPanel>
</Grid>
</Window>

进来下更进一步积累自定义控件的知识,学习自定义面板及构建自定义绘图控件。

创建自定义面板是一种比较常见的自定义控件开发子集。面板驻留一个或多个子元素,并且实现了特定的布局逻辑以恰当地安排其子元素。如果希望构建自己的可拖动的工具栏或可停靠的窗口系统,自定义面板是很重要的元素。当创建需要非标准特定布局的组合控件时,自定义面板通常是很有用的,例如停靠工具栏。

面板在工作时,主要有2件事情:负责改变子元素尺寸和安排子元素的两步布局过程。第一个阶段是测量阶段(measure pass),在这一阶段面板决定其子元素希望具有多大的尺寸。第二个阶段是排列阶段(layout pass),在这一阶段为每个控件指定边界。这两个步骤是必须的,因为在决定如何分割可用空间时,面板需要考虑所有子元素的期望。

可以通过重写MeasureOverride()和ArrangeOverride()方法,为这两个步骤添加自己的逻辑,这两个方法作为WPF布局系统的一部分在FrameworkElement类种定义的。使用MeasureOverride()和ArrangeOverride()方法代替在UIElement类中定义的MeasureCore()和ArrangeCore()。这2个方法是不能被重写的。

接下来我们分析一下这2个方法都在做什么:

1.MeasureOverride()

首先使用MeasureOverride()方法决定每个子元素希望多大的空间,每个MeasureOverride()方法的实现负责遍历子元素集合,并调用每个子元素的Measure()方法。当调用Measure()方法时,需要提供边界框-决定每个子控件最大可用控件的Size对象。在MeasureOverride()方法的最后,面板返回显示所有子元素所需的空间,并返回它们所期望的尺寸。在测量过程的结尾,布局容器必须返回它所期望的尺寸。在简单的面板中,可以通过组合每个资源需要的期望尺寸计算面板所期望的尺寸。

2.ArrangeOverride()方法

测量完所有元素后,就可以在可用的空间中排列元素了。布局系统调用面板的ArrangeOverride()方法,而面板为每个子元素调用Arrange()方法,以告诉子元素为它分配了多大的空间。

当时有Measure()方法测量条目时,传递能够定义可用空间边界的Size对象。当时有Arrange()方法放置条目时,传递能够定义条目尺寸和位置的System.Windows.Rect对象。

了解了这两步,我们来实现一个Canvas面板。

Canvas面板在它们希望的位置放置子元素,并且为子元素设置它们希望的尺寸。所以,Canvas面板不需要计算如何分割可用空间。MeasureOverride()阶段可以为每个子元素提供无限的空间。

  protected override Size MeasureOverride(Size availableSize)
{
Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement element in base.InternalChildren)
{
element.Measure(size);
}
return new Size();
}

在MeasureOverride()方法返回空的Size对象,也就是说Canvas面板不请求任何空间。而是我们明确的为Canvas面板指定尺寸,或者将其放置到布局容器中进行拉伸以填充整个容器的可用空间。

ArrangeOverride()方法包含的内容稍微多一些。为了确定每个元素的正确位置,Canvas面板使用附加属性Left、Right、Top以及Bottom。我们只用Left和Top附加依赖项属性来实现一个简易版。

public class CanvasClone : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
Size size = new Size(double.PositiveInfinity, double.PositiveInfinity);
foreach (UIElement element in base.InternalChildren)
{
element.Measure(size);
}
return new Size();
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (UIElement element in base.InternalChildren)
{
double x = 0;
double y = 0;
double left = Canvas.GetLeft(element);
if (!double.IsNaN(left))
{
x = left;
}
double top = Canvas.GetTop(element);
if(!double.IsNaN(top))
{
y = top;
}
element.Arrange(new Rect(new Point(x, y), element.DesiredSize));
}
return finalSize;
}
}

Xaml代码:

<Window x:Class="CustomElement.CustomPanel"
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:CustomElement"
xmlns:customPanel="clr-namespace:CustomElement.Panels"
mc:Ignorable="d"
Title="CustomPanel" Height="450" Width="800">
<Grid>
<customPanel:CanvasClone>
<TextBlock Text="asg" Canvas.Left="100" Canvas.Top="100"/>
</customPanel:CanvasClone>
</Grid>
</Window>

我们就看到了TextBlock被放置在了靠近左上角100,100的位置。。这里不考虑其他问题,因为只是为了了解自定义面板。

接下来我们创建一个扩展的WrapPanel面板。WrapPanel的工作原理是,该面板逐个布置其子元素,一旦当前行宽度用完,就会切换到下一行。但有时我们需要强制立即换行,以便在新行中启动某个特定控件。尽管WrapPanel面板原本没有提供这一功能,但通过创建自定义控件可以方便地添加该功能。只需要添加一个请求换行的附加依赖项属性即可。此后,面板中的子元素可使用该属性在适当位置换行。

我们添加WrapBreakPanel类,继承自Panel。这里因为要自定义所以不使用代码片段添加附加依赖项属性,而是手写,并设置AffectsMeasure和AffectsArrange为True。我们要在每次LineBreakBefore属性变更时,都触发新的排列阶段。在测量阶段元素按行排列,除非太大或者LineBreakBefore属性设置为true,否则每个元素都被添加到当前行中。

using System;
using System.Windows;
using System.Windows.Controls; namespace CustomElement.Panels
{
public class WrapBreakPanel : Panel
{ static WrapBreakPanel()
{
FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata();
metadata.AffectsArrange = true;
metadata.AffectsMeasure = true;
LineBreakBeforeProperty =
DependencyProperty.RegisterAttached("LineBreakBefore", typeof(bool), typeof(WrapBreakPanel), metadata);
} public static readonly DependencyProperty LineBreakBeforeProperty; public static void SetLineBreakBefore(UIElement element, Boolean value)
{
element.SetValue(LineBreakBeforeProperty, value);
} public static Boolean GetLineBreakBefore(UIElement element)
{
return (bool)element.GetValue(LineBreakBeforeProperty);
} protected override Size MeasureOverride(Size availableSize)
{
Size currentLineSize = new Size();
Size panelSize = new Size(); foreach (UIElement element in base.InternalChildren)
{
element.Measure(availableSize);
Size desiredSize = element.DesiredSize;
if (GetLineBreakBefore(element) || currentLineSize.Width + desiredSize.Width > availableSize.Width)
{
//切换到新行,空间用完,或者通过设置附加依赖项属性请求换行
panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
currentLineSize = desiredSize;
//如果元素太宽无法使用最大行宽进行匹配,则只需要为其指定单独的行。
if (desiredSize.Width > availableSize.Width)
{
panelSize.Width = Math.Max(desiredSize.Width, panelSize.Width);
panelSize.Height += desiredSize.Height;
currentLineSize = new Size();
}
}
else
{
//添加到当前行。
currentLineSize.Width += desiredSize.Width;
//确保线条与最高的元素一样高
currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
}
}
//返回适合所有元素所需的大小。
//通常,这是约束的宽度,高度基于元素的大小。
//但是,如果一个元素的宽度大于面板的宽度。
//所需的宽度将是该行的宽度。
panelSize.Width = Math.Max(currentLineSize.Width, panelSize.Width);
panelSize.Height += currentLineSize.Height;
return panelSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
int firstInLine = 0;
Size currentLineSize = new Size();
//积累高度
double accumulatedHeight = 0;
UIElementCollection elements = base.InternalChildren;
for (int i = 0; i < elements.Count; i++)
{
Size desiredSize = elements[i].DesiredSize;
if (GetLineBreakBefore(elements[i]) || currentLineSize.Width + desiredSize.Width > finalSize.Width)
{
//换行
arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, i);
accumulatedHeight += currentLineSize.Height;
currentLineSize = desiredSize;
if (desiredSize.Width > finalSize.Width)
{
arrangeLine(accumulatedHeight, desiredSize.Height, i, ++i);
accumulatedHeight += desiredSize.Height;
currentLineSize = new Size();
}
firstInLine = i;
}
else
{
//继续当前前行。
currentLineSize.Width += desiredSize.Width;
currentLineSize.Height = Math.Max(desiredSize.Height, currentLineSize.Height);
}
}
if (firstInLine < elements.Count)
{
arrangeLine(accumulatedHeight, currentLineSize.Height, firstInLine, elements.Count);
}
return finalSize;
}
private void arrangeLine(double y, double lineHeight, int start, int end)
{
double x = 0;
UIElementCollection children = InternalChildren;
for (int i = start; i < end; i++)
{
UIElement child = children[i];
child.Arrange(new Rect(x, y, child.DesiredSize.Width, lineHeight));
x += child.DesiredSize.Width;
}
}
}
}

调用的XAML代码:

<Window x:Class="CustomElement.CustomPanel"
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:CustomElement"
xmlns:customPanel="clr-namespace:CustomElement.Panels"
mc:Ignorable="d"
Title="CustomPanel" Height="450" Width="800">
<Grid>
<customPanel:CanvasClone>
<TextBlock Text="asg" Canvas.Left="100" Canvas.Top="100"/>
</customPanel:CanvasClone>
<customPanel:WrapBreakPanel>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button customPanel:WrapBreakPanel.LineBreakBefore="True" FontWeight="Bold" Content="Button with Break"/>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
<Button>No Break Here</Button>
</customPanel:WrapBreakPanel>
</Grid>
</Window>

在MeasureOverride阶段,主要是测量元素的位置和是否需要换行。在ArrangeOverride阶段,每计算满显示一行的元素后,就开始绘制这一行。这里只需要了解着这个MeasureOverride和ArrangeOverride在干什么就行。目前这里不需要掌握,因为后面会讲到列表虚拟化和数据虚拟化,会更详细的讲相关的设计内容。这里只要直到,有自定义面板可以自己设计列表的呈现,就可以了。

我创建了一个C#相关的交流群。用于分享学习资料和讨论问题,这个propuev也在群文件里。欢迎有兴趣的小伙伴:QQ群:542633085

WPF教程十三:自定义控件进阶可视化状态与自定义Panel的更多相关文章

  1. [Aaronyang] 写给自己的WPF4.5 笔记13[二维自定义控件技巧-可视化状态实战,自定义容器,注册类命令,用户控件补充]

     我的文章一定要做到对读者负责,否则就是失败的文章  ---------   www.ayjs.net    aaronyang技术分享 博文摘要:欢迎大家来支持我的<2013-2015 Aar ...

  2. WPF教程十二:了解自定义控件的基础和自定义无外观控件

    这一篇本来想先写风格主题,主题切换.自定义配套的样式.但是最近加班.搬家.新租的房子打扫卫生,我家宝宝6月中旬要出生协调各种的事情,导致了最近精神状态不是很好,又没有看到我比较喜欢的主题风格去模仿的, ...

  3. 【WPF学习】第六十六章 支持可视化状态

    上一章介绍的ColorPicker控件,是控件设计的最好示例.因为其行为和可视化外观是精心分离的,所以其他设计人员可开发动态改变其外观的新模板. ColorPicker控件如此简单的一个原因是不涉及状 ...

  4. WPF教程十一:简单了解并使用控件模板

    WPF教程十一:简单了解并使用控件模板 这一章梳理控件模板,每个WPF控件都设计成无外观的,但是行为设计上是不允许改变的,比如使用Button的控件时,按钮提供了能被点击的内容,那么自由的改变控件外观 ...

  5. Unity3D嵌入WPF教程

    Unity3D嵌入WPF教程 创建一个 类库工程 添加 WindowForm 用户控件 (UserControl) 1).引入 UntiyWebPlayer COM 组件 在工具->选择工具箱中 ...

  6. Android开发技巧——自定义控件之增加状态

    Android开发技巧--自定义控件之增加状态 题外话 这篇本该是上周四或上周五写的,无奈太久没写博客,前几段把我的兴头都用完了,就一拖再拖,直到今天.不想把这篇拖到下个月,所以还是先硬着头皮写了. ...

  7. WPF 精修篇 自定义控件

    原文:WPF 精修篇 自定义控件 自定义控件 因为没有办法对界面可视化编辑 所以用来很少 现在实现的是 自定义控件的 自定义属性 和自定义方法 用VS 创建自定义控件后 会自动创建 Themes 文件 ...

  8. WPF教程十四:了解元素的渲染OnRender()如何使用

    上一篇分析了WPF元素中布局系统的MeasureOverride()和ArrangeOverride()方法.本节将进一步深入分析和研究元素如何渲染它们自身. 大多数WPF元素通过组合方式创建可视化外 ...

  9. CRL快速开发框架系列教程十三(嵌套查询)

    本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...

随机推荐

  1. 大作!webpack详细配置

    webpack学习之旅 好好学习 天天向上!遇到bug,不要慌! 文章目录 webpack学习之旅 大一统的模块化规范--ES6模块化 1.node.js中通过babel体验ES6模块化 2.ES6模 ...

  2. .NET平台系列12 .NET未来之开源.NET Core

    系列目录     [已更新最新开发文章,点击查看详细] 微软于2014年11月推出了.NET Core 1.0..NET Core的目标是从我们在过去12年中对.NET Framework的构建.交付 ...

  3. Archlinux+win10双系统扩容Boot/ESP分区

    环境 系统:Archlinux + Windowns10 双系统 软件:MiniTool Partition Wizard 免费版 + Diskgenius 免费版 分区:原ESP分区100M 原恢复 ...

  4. CVPR2020论文解读:OCR场景文本识别

    CVPR2020论文解读:OCR场景文本识别 ABCNet:  Real-time Scene Text Spotting with Adaptive Bezier-Curve Network∗ 论文 ...

  5. Angel图算法

    Angel图算法 [2.0]CommonFriends 计算两个好友的共同好友数,某种程度上可以刻画两个节点之间的紧密程度. 输入 输入数据路径:输入文件所在路径,无权网络数据, 数据格式为两列 sr ...

  6. IPv6 与 IPv4现状

    IPv6 与 IPv4现状 一.概述 (1) IPv4可提供bai4,294,967,296个地址,IPv6将原来的32位地址空间增大du到128位,数目是zhi2的128次方.能够对地球上每平方米d ...

  7. 35 张图带你 MySQL 调优

    这是 MySQL 基础系列的第四篇文章,之前的三篇文章见如下链接 138 张图带你 MySQL 入门 47 张图带你 MySQL 进阶!!! 炸裂!MySQL 82 张图带你飞 一般传统互联网公司很少 ...

  8. 教你在Kubernetes中快速部署ES集群

    摘要:ES集群是进行大数据存储和分析,快速检索的利器,本文简述了ES的集群架构,并提供了在Kubernetes中快速部署ES集群的样例:对ES集群的监控运维工具进行了介绍,并提供了部分问题定位经验,最 ...

  9. Floyd最短路及路径输出

    引例 下图表示城市之间的交通路网,线段上的数字表示费用.如图,求$V_{1}$→$V_{n}$最短路径长度及路径 样例数据 输入 10 0 2 5 1 0 0 0 0 0 0 0 0 0 0 12 1 ...

  10. 彻底搞懂彻底搞懂事件驱动模型 - Reactor

    在高性能网络技术中,大家应该经常会看到Reactor模型.并且很多开源软件中都使用了这个模型,如:Redis.Nginx.Memcache.Netty等. 刚开始接触时可能一头雾水,这到底是个什么东东 ...