WPF——样式与模板

24 篇文章 16 订阅

一、引言

每次写blog一定是我遇到什么实际问题了。而这次的问题,和文章标题没有特别大的关系。因为上一篇文我讲到了自定义日历控件(Calendar)的事,在我改CalendarDayButtonStyle的过程中,我发现默认的Style中出现了大量的VisualState标签。但正如上文中所说,一个自身的WPF程序员或许对VisualState相关类也会感到陌生,更何况我呢。于是我决定,对VisualState相关类进行学习。

我打开微软官网,搜了下VisualState,首先在Windows App SDK的内容中看到了它,但是我现在是WPF中遇到的它丫。虽然从内容上看起来差不多,但是我还是想在WPF的相关条目里找到它。于是我又看了几篇文。在WPF的《Styles and Templates》章节中看到了VisualState的出现。但是,这个章节主要是介绍样式与模板的,VisualState的内容只有一点点。那怎么办?我想了想,反正样式与模板也没系统学过,用倒是已经在用了,不如借此机会,也好好学习一下。

二、样式与模板

WPF的样式与模板是一系列让开发和设计人员为他们的产品创建视觉上引人注目效果和一致外观的特性。当定制一个程序的外观时,你想要一个强大的样式与模板模型,它们能支持在程序内和程序间维护和共享外观。WPF就提供了这样的模型。

两个长难句,意思很简单,样式(style)和模板(Template)就是通用化外观的工具。

WPF样式模型的另一个特性是表示(Presentation)与逻辑(logic)的分离。

表示可以认为是程序的UI,逻辑可以认为是业务逻辑(后台逻辑)。
在传统WinForms中,在界面上拖拽控件,控件后台处理逻辑,而逻辑中有时也会有更改控件外观的代码,这就是把表示与逻辑给混起来用了。这样混合的方式在小项目或demo中,确实很方便,但是对于分工、对于可维护性,对于整个程序的结构来讲,却大打折扣了。所以将表示和逻辑分离是一种进步的体现。

设计人员可以只使用XAML来处理应用程序的外观,而开发人员可以使用C#或VB来处理编程逻辑。(这种想法在国内是挺理想化的,反正据我了解,大部分公司WPF开发前后台还是同一批人做的。但是使程序的分层更清晰确实是达到了。)

这篇文集中在程序的样式与模板方面,不讨论任何数据绑定的概念。(所以你最好要有一定的WPF基础,至少对Binding有个大概的概念)

还有学之前,最好把资源(Resource)了解一下,因为资源可以重用样式和模板。

1. 示例程序 Sample

本文中示例是一个基于简单照片的浏览程序,如下图所示:
在这里插入图片描述
这个简单的照片示例程序使用样式和模板创建视觉上良好的用户体验。该示例有两个TextBlock元素和一个绑定到图像列表的ListBox控件。

2. 样式 Style

你可以将Style(样式)视作一组属性值,它是一种能应用到多个元素上的便捷方法。你可以在派生自FrameworkElement或FrameworkContentElement的任何元素上使用Style,比如Window或Button。

声明一个样式最常见的方式是在XAML文件中的Resources段中用作资源。因为样式是资源,所以它们遵循适用于资源的所有规则。简单说,在哪里声明样式就会影响那里的应用样式。例如,如果你在程序(app)定义XAML文件的根元素中声明了一个Style,该Style就能在程序的任何地方使用。

例如,下面的XAML代码为TextBlock声明了两种样式,一种自动应用于所有TextBlock元素,另一种必须显式引用。

<!--在Window元素的资源中添加样式,并指定作用目标,这是一种很常用的方式-->
<Window.Resources>
    <!-- .... other resources .... -->

    <!--A Style that affects all TextBlocks-->
    <Style TargetType="TextBlock">
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="FontFamily" Value="Comic Sans MS"/>
        <Setter Property="FontSize" Value="14"/>
    </Style>
    
    <!--A Style that extends the previous TextBlock Style with an x:Key of TitleText-->
    <!--BaseOn有继承的意味,就是在原样式的基础上做修改-->
    <Style BasedOn="{StaticResource {x:Type TextBlock}}"
           TargetType="TextBlock"
           x:Key="TitleText">
        <Setter Property="FontSize" Value="26"/>
        <Setter Property="Foreground">
            <Setter.Value>
                <LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
                    <LinearGradientBrush.GradientStops>
                        <GradientStop Offset="0.0" Color="#90DDDD" />
                        <GradientStop Offset="1.0" Color="#5BFFFF" />
                    </LinearGradientBrush.GradientStops>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

下面是一个使用上文声明样式的例子:

<StackPanel>
    <TextBlock Style="{StaticResource TitleText}" Name="textblock1">My Pictures</TextBlock>
    <TextBlock>Check out my new pictures!</TextBlock>
</StackPanel>

在这里插入图片描述

3. 控件模板 ControlTemplates

在WPF中,控件的ControlTemplate定义了控件的外观。你可以通过定义新的ControlTemplate并将它分配给指定控件来改变控件的结构和外观。在许多情况下,模板为你提供了足够的灵活性,以至于你不需要编写自定义控件。

每个控件都有分配给该控件的Control.Template的默认模板。模板将控件的视觉外观与控件的功能连接起来。因为是在XAML中定义模板的,所以不需要编写任何代码就可以改变控件的外观。每个模板都是为特定控件设计的,比如Button。

通常,你可以在XAML文件的Resources部分将模板声明为资源。它所适用的规则就和所有资源一样。

控件模板比样式要复杂许多。这是因为控件模板重写了整个控件的视觉外观(visual appearance),而样式只是将现有控件的属性更改。不过,因为控件的模板是过设置Control.Template属性来应用的,所以你可以使用样式来定义或设置一个模板。

尽管Style是针对控件的属性的,
而Template是重写整个控件结构的,
就这一点来讲模板显然比样式复杂,但是模板本身也是控件的一个属性,所以Style也可以指定更改Control.Template,这个角度来看,好像也没有谁一定比谁复杂了?

Designers(设计器)通常允许你创建现有模板的副本并修改它。例如。在Visual Studio WPF设计器中,选择一个CheckBox控件,然后右键单击并选择编辑模板>创建一个副本。一顿操作下来就会生成定义模板的样式。

这种方法常用于修改一些自带的或第三方的控件,因为这些控件通常比较复杂,要从零开始实现并做修改比较花时间,所以你可以通过上面操作来生成它们的样式,然后更改其中部分既可。

<Style x:Key="CheckBoxStyle1" TargetType="{x:Type CheckBox}">
    <Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual1}"/>
    <Setter Property="Background" Value="{StaticResource OptionMark.Static.Background1}"/>
    <Setter Property="BorderBrush" Value="{StaticResource OptionMark.Static.Border1}"/>
    <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
    <Setter Property="BorderThickness" Value="1"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type CheckBox}">
                <Grid x:Name="templateRoot" Background="Transparent" SnapsToDevicePixels="True">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Border x:Name="checkBoxBorder" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                        <Grid x:Name="markGrid">
                            <Path x:Name="optionMark" Data="F1 M 9.97498,1.22334L 4.6983,9.09834L 4.52164,9.09834L 0,5.19331L 1.27664,3.52165L 4.255,6.08833L 8.33331,1.52588e-005L 9.97498,1.22334 Z " Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="1" Opacity="0" Stretch="None"/>
                            <Rectangle x:Name="indeterminateMark" Fill="{StaticResource OptionMark.Static.Glyph1}" Margin="2" Opacity="0"/>
                        </Grid>
                    </Border>
                    <ContentPresenter x:Name="contentPresenter" Grid.Column="1" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="HasContent" Value="true">
                        <Setter Property="FocusVisualStyle" Value="{StaticResource OptionMarkFocusVisual1}"/>
                        <Setter Property="Padding" Value="4,-1,0,0"/>

... content removed to save space ...

编辑模板的副本是了解模板工作方式的好方法。与创建新的空白模板相比,编辑模板和更改视觉表示的一部分显然更容易。

很多控件看起来很难实现,你生成它的副本后一看便知它内部结构,所以该方法也是学习控件实现思路的好方法。

3.1. TemplateBinding

你可能已经注意到,在上一节中定义的模板资源使用了TemplateBinding标记拓展。TemplateBinding是针对模板场景的一种优化形式的绑定,类似于使用

{Binding RelativeSource = {RelativeSource TemplatedParent}}

构造的绑定。TemplateBinding用于将模板的部分绑定到控件的属性。例如,每个控件都有一个BorderThickness属性。使用TemplateBinding来管理模板中受该控件设置影响的元素。

3.2. ContentControl和ItemsControl

如果一个ContentPresenter被声明在ContentControl的ControlTemplate中,那么该ContentPresenter会自动绑定到ContentTemplate和Content属性。同样,ItemsControl的ControlTemplate中的ItemsPresenter会自动绑定到ItemTemplate和Items属性。

4. 数据模板 DataTemplates

在本例的程序中,有一个ListBox控件,它绑定了一个照片的列表。

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}"
         Background="Silver" Width="600" Margin="10" SelectedIndex="0"/>

该ListBox现在看起来如下:
在这里插入图片描述
大部分控件都有某种类型的内容,而这些内容通常来自你绑定的数据。在本例中,数据是照片列表。在WPF中,使用DataTemplate来定义数据的视觉表示(即外观)。基本地,你放入DataTemplate的内容就决定了数据在渲染程序中的样子。

在示例程序中,每个自定义的Photo对象都有一个string类型的Source属性,用于指定图像文件的路径。现在,照片对象作为文件路径而呈现。

public class Photo
{
    public Photo(string path)
    {
        Source = path;
    }

    public string Source { get; }

    public override string ToString() => Source;
}

为了让照片以图像的形式显示,你需要创建一个DataTemplate作为资源。

<Window.Resources>
    <!-- .... other resources .... -->

    <!--DataTemplate to display Photos as images
    instead of text strings of Paths-->
    <DataTemplate DataType="{x:Type local:Photo}">
        <Border Margin="3">
            <Image Source="{Binding Source}"/>
        </Border>
    </DataTemplate>
</Window.Resources>

注意,DataType属性类似于Style的TargetType属性。如果你的DataTemplate在Resources中,当你为某个类型指定DataType属性并省略x:Key时,只要该类型出现,就会应用DataTemplate。当然,你始终可以选择使用x:Key来分配DataTemplate,然后将它设置给StaticResource(用于接受DataTemplate类型的属性,例如,ItemTemplate属性或ContentTemplate属性)。

本质上来讲,上面示例中的DataTemplate定义了“只要有Photo对象出现,它就应该表现为Border中的Image”。在该DataTemplate中,程序现在看起来是这样的:
在这里插入图片描述
数据模板(DataTemplate)模型提供了其他特性。例如,如果你正在使用HeaderedItemsControl类型(如Menu或TreeView)来显示包含其他集合的集合数据,则会有HierarchicalDataTemplate。另一个数据模板特性是DataTemplateSelector,它允许你根据自定义逻辑来选择使用的DataTemplate。

5. 触发器 Triggers

当属性值发生变化或引发事件时,trigger(触发器)会设置属性或启动动作,例如动画。Style、ControlTemplate和DataTemplate都有一个可以包含一组触发器的属性Triggers。触发器有多种类型。

5.1. PropertyTriggers

根据属性的值来设置属性值或启动动作(action)的触发器称为属性触发器,它就用Trigger来表示。

要演示如何使用属性触发器,可以让ListBoxItem部分透明(除非它被选中)。下面样式将ListBoxItem的Opacity(不透明度,1为不透明,0为全透明)设置为0.5;当IsSelected属性为true时,不透明度设为1.0。

<Window.Resources>
    <!-- .... other resources .... -->

    <Style TargetType="ListBoxItem">
        <Setter Property="Opacity" Value="0.5" />
        <Setter Property="MaxHeight" Value="75" />
        <Style.Triggers>
            <Trigger Property="IsSelected" Value="True">
                <Trigger.Setters>
                    <Setter Property="Opacity" Value="1.0" />
                </Trigger.Setters>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>

本例使用一个Trigger来设置属性值,但是要注意,Trigger类还有EnterActions和ExitActions属性,这些属性使能触发器来执行操作。

注意,ListBoxItem的MaxHeight属性被设置为75。在下图中,第三项是被选中的。

在这里插入图片描述

5.2. EventTriggers和Storyboards

另一种类型的触发器是EventTrigger(事件触发器),它会根据事件的发生来启动一组动作。例如,下面的EventTrigger对象指定了当鼠标指针进入ListBoxItem区域时,MaxHeight属性在0.2s内会发生动画,值变为90。当鼠标离开选项时,属性会在1s内变回初始值。注意,不需要为MouseLeave动画指定目标值,因为动画能够自行跟踪到原始值。

<Style.Triggers>
    <Trigger Property="IsSelected" Value="True">
        <Trigger.Setters>
            <Setter Property="Opacity" Value="1.0" />
        </Trigger.Setters>
    </Trigger>
    <EventTrigger RoutedEvent="Mouse.MouseEnter">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:0.2"
                        Storyboard.TargetProperty="MaxHeight"
                        To="90"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
    <EventTrigger RoutedEvent="Mouse.MouseLeave">
        <EventTrigger.Actions>
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation
                        Duration="0:0:1"
                        Storyboard.TargetProperty="MaxHeight"  />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>
</Style.Triggers>

在下图中,鼠标指向了第三项:
在这里插入图片描述

5.3. MultiTriggers、DataTriggers和MultiDataTriggers

除了Trigger和EventTrigger,还有其他类型的触发器。MultiTrigger允许你基于多个条件来设置属性值。当条件的属性是数据绑定时(data-bound),可以使用DataTrigger和MultiDataTrigger。

⭐6. 视觉状态 Visual States

控件始终是处于特定状态(specific state)中的。例如,当鼠标移至控件表面时,该控件就被认为处于MouseOver的常见状态。没有特定状态的控件被认为处于普通Normal状态。状态可以分组,前面提到的状态都是CommonStates这个状态组的一部分。大部分控件有两个状态组:CommonStates和FocusStates。在应用于控件的每个状态组中,控件始终处于每个组中的一个状态,例如CommonStates.MouseOver和FocusStates.Unfocused。一个控件不能在同一个组中处于两个不同的状态,例如不能处在CommonStates.Normal和CommonStates.Disabled中。下面是大多数控件能识别和使用的状态表:

VisualState NameVisualStateGroup Name描述
NormalCommonStates默认状态
MouseOverCommonStates鼠标指针在控件上
PressedCommonStates控件被点击
DisabledCommonStates控件被禁用
FocusedFocusStates控件有焦点
UnfocusedFocusStates控件无焦点

通过在控件模板的根元素上定义System.Windows.VisualStateManager,可以在控件进入特定状态时触发动画。VisualStateManager声明了要监视的VisualStateGroup和VisualState的组合。当控件进入监视状态时,VisualStateManager定义的动画就会启动。

例如,下面的XAML代码监视CommonStates.MouseOver状态,使得名为backgroundElement的元素的填充色发生动画。当控件回到CommonStates.Normal状态时,恢复backgroundElement元素的填充色。

<ControlTemplate x:Key="roundbutton" TargetType="Button">
    <Grid>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup Name="CommonStates">
                <VisualState Name="Normal">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="{TemplateBinding Background}"
                                    Duration="0:0:0.3"/>
                </VisualState>
                <VisualState Name="MouseOver">
                    <ColorAnimation Storyboard.TargetName="backgroundElement"
                                    Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)"
                                    To="Yellow"
                                    Duration="0:0:0.3"/>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        ...

7. 共享资源与主体 Shared resources和themes

一个典型的WPF程序可能有多个应用于整个程序的UI资源。总的来说,这组资源可以被认为是程序的主体。WPF通过使用封装为ResourceDictionary类的资源词典,来支持将UI资源打包为主题。

WPF主题是通过使用样式和模板机制来定义的,WPF公开了这些机制来定制任何元素的视觉效果。

WPF主体资源存储在嵌入式资源词典中。这些资源词典必须嵌入到已签名的程序集中,并且可以嵌入到与代码本身相同的程序集中,也可以嵌入到并列(side-by-side)的程序集中。PresentationFramework.dll,该程序集包含了WPF控件,主题资源位于一系列并列的程序集中。

当搜索元素的样式时,主题将是最后查找的位置。通常情况下,搜索将沿着元素树向上爬行以找到适当的资源,若找不到,则再查找程序的资源集,最后再查找系统。这给了程序开发者一个机会,使得搜索在到达主题之前,可以为树上的或程序上的任何对象重新定义样式。

你可以将资源词典定义为单独的文件,这样就能在多个程序中重用一个主题了。你还可以通过定义多个资源词典来创建可切换的主题,这些资源词典提供相同类型的资源,但是值不同。在应用程序的层级重新定义样式或其他资源是程序换肤推荐的方法。

要跨程序共享一组包含样式和模板的资源,可以创建一个XAML文件,并定义一个ResourceDictionary,该资源词典包含对share.xaml的引用。

<ResourceDictionary.MergedDictionaries>
  <ResourceDictionary Source="Shared.xaml" />
</ResourceDictionary.MergedDictionaries>

它是share.xaml的共享,它本身定义了一个包含一组样式和笔刷资源的ResourceDictionary,使程序的控件有着一致的外观。


三、结尾

通过本文的学习,重温了样式、控件模板、数据模板和触发器,同时对视觉状态、共享资源和主题的概念进行了初步了解。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF编程宝典——使用C# 2008和.NET 3.5(第2版)英文版 目录 第1章 WPF概述 1 1.1 理解Windows图形 1 1.1.1 DirectX:新的图形引擎 1 1.1.2 硬件加速与WPF 2 1.2 WPF:高级API 4 1.2.1 分辨率无关性 5 1.2.2 WPF的演化 9 1.2.3 Windows窗体将继续保留 11 1.2.4 DirectX也将继续保留 12 1.2.5 Silverlight 12 1.3 WPF体系结构 13 1.4 结束语 17 第2章 XAML 19 2.1 理解XAML 20 2.1.1 WPF之前的图形用户界面 20 2.1.2 XAML变体 21 2.1.3 XAML编译 22 2.2 XAML基础 22 2.2.1 XAML名称空间 23 2.2.2 后台代码类 24 2.3 XAML中的属性和事件 26 2.3.1 简单属性与类型转换器 27 2.3.2 复杂属性 29 2.3.3 标记扩展 30 2.3.4 附加属性 31 2.3.5 嵌套元素 32 2.3.6 特殊字符与空白字符 35 2.3.7 事件 36 2.3.8 完整的Eight Ball示例程序 38 2.4 使用其他名称空间中的类型 38 2.5 加载和编译XAML 40 2.5.1 只使用代码 41 2.5.2 使用代码和未编译的XAML 43 2.5.3 使用代码和编译过的XAML 44 2.5.4 只使用XAML 46 2.6 结束语 47 第3章 Application类 48 3.1 应用程序的生命周期 48 3.1.1 创建Application对象 48 3.1.2 派生一个自定义的 Application类 49 3.1.3 应用程序的关闭方式 51 3.1.4 应用程序事件 51 3.2 Application类的任务 53 3.2.1 处理命令行参数 54 3.2.2 访问当前Application对象 55 3.2.3 在窗口之间进行交互 56 3.2.4 单实例应用程序 57 3.3 结束语 63 第4章 布局 64 4.1 理解WPF中的布局 64 4.1.1 WPF布局原则 65 4.1.2 布局过程 66 4.1.3 布局包容器 66 4.2 使用StackPanel面板 进行简单布局 68 4.2.1 布局属性 69 4.2.2 对齐方式 70 4.2.3 外边距 71 4.2.4 最小尺寸、最大尺寸以及 显式地设置尺寸 72 4.3 WrapPanel面板和DockPanel 面板 74 4.3.1 WrapPanel面板 74 4.3.2 DockPanel面板 75 4.3.3 嵌套布局包容器 77 4.4 Grid面板 78 4.4.1 调整行和列 80 4.4.2 跨越行和列 82 4.4.3 分割窗口 83 4.4.4 共享尺寸组 86 4.4.5 UniformGrid面板 88 4.5 使用Canvas面板进行 基于坐标的布局 89 4.5.1 Z顺序 90 4.5.2 lnkCanvas元素 91 4.6 布局示例 93 4.6.1 列设置 93 4.6.2 动态内容 94 4.6.3 组合式用户界面 96 4.7 结束语 97 第5章 内容 99 5.1 理解内容控件 99 5.1.1 Content属性 101 5.1.2 对齐内容 102 5.1.3 WPF内容原则 103 5.2 特殊包容器控件 104 5.2.1 ScrollViewer包容器控件 104 5.2.2 GroupBox和TabItem: 带标题的内容控件 107 5.2.3 Expander控件 109 5.3 装饰控件 112 5.3.1 Border控件 112 5.3.2 Viewbox控件 113 5.4 结束语 115 第6章 依赖项属性和路由事件 116 6.1 理解依赖项属性 116 6.1.1 定义和注册依赖项属性 117 6.1.2 WPF使用依赖项属性的方式 124 6.2 理解路由事件 126 6.2.1 定义和注册路由事件 126 6.2.2 关联事件处理程序 128 6.2.3 事件路由 129 6.3 WPF事件 137 6.3.1 生命周期事件 137 6.3.2 输入事件 139 6.3.3 键盘输入 140 6.3.4 鼠标输入 145 6.4 结束语 149 第7章
12篇学通csharp网络编程——第四篇 TCP应用编程 12篇学通csharp网络编程——第三篇 HTTP应用编程(下) 12篇学通csharp网络编程——第二篇 HTTP应用编程(上) 12篇学通csharp网络编程——第一篇 基础之进程线程 Lucene(1)lucene,你也会(7篇)——第一篇 快速入门 MongoDB(8)8天学通MongoDB——第八天 驱动实践 8天学通MongoDB——第七天 运维技术 8天学通MongoDB——第六天 分片技术 8天学通MongoDB——第五天 主从复制 8天学通MongoDB——第四天 索引操作 8天学通MongoDB——第三天 细说高级操作 8天学通MongoDB——第二天 细说增删查改 8天学通MongoDB——第一天 基础入门 UML系列(4)团队沟通利器之UML——类图 团队沟通利器之UML—— 序列图 团队沟通利器之UML——用例图 团队沟通利器之UML——活动图 wcf系列(5)wcf系列学习5天速成——第五天 服务托管 wcf系列学习5天速成——第四天 wcf之分布式架构 wcf系列学习5天速成——第三天 事务的使用 wcf系列5天速成——第二天 binding的使用(2) wcf系列5天速成——第一天 binding的使用(1) wpf系列(8)8天入门wpf—— 第八天 最后的补充 8天入门wpf—— 第七天 画刷 8天入门wpf—— 第六天 细说控件 8天入门wpf—— 第五天 数据绑定 8天入门wpf—— 第四天 模板 8天入门wpf—— 第三天 样式 8天入门wpf—— 第二天 xaml详解 8天入门wpf—— 第一天 基础概念介绍 并行开发(8)8天玩转并行开发——第八天 用VS性能向导解剖你的程序 8天玩转并行开发——第七天 简要分析任务与线程池 8天玩转并行开发——第六天 异步编程模型 8天玩转并行开发——第五天 同步机制(下) 8天玩转并行开发——第四天 同步机制(上) 8天玩转并行开发——第三天 plinq的使用 8天玩转并行开发——第二天 Task的使用 8天玩转并行开发——第一天 Parallel的使用 多线程系列(5)5天不再惧怕多线程——第五天 线程池 5天不再惧怕多线程——第四天 信号量 5天不再惧怕多线程——第三天 互斥体 5天不再惧怕多线程——第二天 锁机制 5天不再惧怕多线程——第一天 尝试Thread 经典算法专题(21)经典算法题每日演练——第二十一题 十字链表 经典算法题每日演练——第二十题 三元组 经典算法题每日演练——第十九题 双端队列 经典算法题每日演练——第十八题 外排序 经典算法题每日演练——第十七题 Dijkstra算法 经典算法题每日演练——第十六题 Kruskal算法 经典算法题每日演练——第十五题 并查集 经典算法题每日演练——第十四题 Prim算法 经典算法题每日演练——第十三题 赫夫曼树 经典算法题每日演练——第十二题 线段树 经典算法题每日演练——第十一题 Bitmap算法 经典算法题每日演练——第十题 树状数组 经典算法题每日演练——第九题 优先队列 经典算法题每日演练——第八题 AC自动机 经典算法题每日演练——第七题 KMP算法 经典算法题每日演练——第六题 协同推荐SlopeOne 算法 经典算法题每日演练——第五题 字符串相似度 经典算法题每日演练——第四题 最长公共子序列 经典算法题每日演练——第三题 猴子吃桃 经典算法题每日演练——第二题 五家共井 经典算法题每日演练——第一题 百钱买百鸡 开发利器系列(1)介绍一个小工具 Linqer 那点所谓的分布式(2)那点所谓的分布式——memcache 那点所谓的分布式——redis 树结构专题(5)6天通吃树结构—— 第五天 Trie树 6天通吃树结构—— 第四天 伸展树 6天通吃树结构—— 第三天 Treap树 6天通吃树结构—— 第二天 平衡二叉树 6天通吃树结构—— 第一天 二叉查找树 算法速成系列(15)算法系列15天速成——第十五天 图【下】(大结局) 算法系列15天速成——第十四天 图【上】 算法系列15天速成——第十三天 树操作【下】 算法系列15天速成——第十二天 树操作【中】 算法系列15天速成——第十一天 树操作(上) 算法系列15天速成——第十天 栈 算法系列15天速成——第九天 队列 算法系列15天速成——第八天 线性表【下】 算法系列15天速成——第七天 线性表【上】 算法系列15天速成——第六天 五大经典查找【下】 算法系列15天速成——第五天 五大经典查找【中】 算法系列15天速成——第四天 五大经典查找【上】 算法系列15天速成——第三天 七大经典排序【下】 算法系列15天速成——第二天 七大经典排序【中】 算法系列15天速成——第一天 七大经典排序【上】 算法洗脑系列(8)算法洗脑系列(8篇)——第八篇 概率思想 算法洗脑系列(8篇)——第七篇 动态规划 算法洗脑系列(8篇)——第六篇 回溯思想 算法洗脑系列(8篇)——第五篇 分治思想 算法洗脑系列(8篇)——第四篇 枚举思想 算法洗脑系列(8篇)——第三篇 贪心思想 算法洗脑系列(8篇)——第二篇 递归思想 算法洗脑系列(8篇)——第一篇 递推思想 天籁数学(3)天籁数学——数列篇(3) 天籁数学——数列篇(2) 天籁数学——数列篇(1) 图形图像(1)玩玩图形图像——第一篇:图片灰度化 小爬虫系列(4)玩玩小爬虫——抓取时的几个小细节 玩玩小爬虫——抓取动态页面 玩玩小爬虫——试搭小架构 玩玩小爬虫——入门

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值