原文:WPF范围选择控件(RangeSelector)

版权声明:本文为博主原创文章,转载请注明作者和出处 https://blog.csdn.net/ZZZWWWPPP11199988899/article/details/78084886

       在某些应用场景中,我们需要做可视化的范围选择。例如,在进行录像剪辑的时候,我们希望在播放时间轴上通过拖动两个可移动的控件来确定两控件之间的时间轴为我们希望进行录像剪辑的时间范围。WPF中并没有这样的预定义控件,所以如果需要有这样的应用场景,则需要自定义这样的控件。本文便是简述定制这样一个控件的基本的思路。

       一 基本结构

      

       先来看一下这样一个控件的基本结构,如上图所示,总体可分为4个部分,1是整个可选择的范围,2是选中的范围,3是左右两个选择器,可在选择范围轴上移动,4是选择信息显示按钮。

       从控件构成来说,1、2都可以用Path来实现,3的上下两个部分也可以用Path实现,4则是一个TextBlock(之所以不选择Label,是希望能用到TextBlock的TextTrimming属性)。

       二 代码结构

       

       为了便于复用,我将此控件单独封装成了一个库(如有需要,也可以很方便的与其他自定义空间库合并),总体上代码的结构非常简单:一个RangeSelector类的cs代码文件RangeSelector.cs,用于控件的逻辑控制;一个控件默认模板的xaml代码文件RangeSelector.xaml;另外还有三个用于控件辅助控制的数据转换类(Converter)。

       

       三 默认模板

       RangeSelector.xaml定义了控件的默认外观,根据(一)里的基本结构,控件必须要包含以下几个命名部分:

       PART_Range:为Path控件,用于展示总的选择范围。

       PART_Canvas:为Canvas控件,用于承载其他绘制控件的容器,之所以选择Canvas,因为他可以通过SetLeft和SetTop方法方便的设置控件的绝对位置,为选择器的移动提供了方便。

       PART_SelectedRange:为Path控件,选中的范围。

       PART_RangeSelector1/PART_RangeSelector2:范围选择器,本文用两个Path组合的Grid来实现。

       PART_LowerMessageTextBlock/PART_UpperMessageTextBlock:为TextBlock控件,用于显示选择的范围信息。

       具体代码如下:

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RangeSelectors"
xmlns:cvt="clr-namespace:RangeSelectors.Converter"> <cvt:DoubleToGridLengthConverter x:Key="doubleToGridLengthConverter"/>
<cvt:RangePathMarginConverter x:Key="rangePathMarginConverter"/>
<cvt:SelectorUpShapeConverter x:Key="selectorUpShapeConverter"/>
<cvt:SelectorDownShapeConverter x:Key="selectorDownShapeConverter"/> <Style TargetType="{x:Type local:RangeSelector}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:RangeSelector}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions> <Path x:Name="PART_Range" Grid.Row="1" Panel.ZIndex="0"
Fill="{TemplateBinding RangeColor}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Stretch="Fill">
<Path.Margin>
<MultiBinding Converter="{StaticResource rangePathMarginConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Margin>
</Path> <Canvas x:Name="PART_Canvas" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top"> <Path x:Name="PART_SelectedRange" Grid.Row="1" Panel.ZIndex="1"
Fill="{TemplateBinding SelectedRangeColor}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Stretch="Fill">
<Path.Margin>
<MultiBinding Converter="{StaticResource rangePathMarginConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Margin>
</Path> <Grid x:Name="PART_RangeSelector1" Panel.ZIndex="0"
Canvas.Left="0" Canvas.Top="0" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="{TemplateBinding RangeBarHeight, Converter={StaticResource doubleToGridLengthConverter}}"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Path x:Name="pathSelectorUp1" Grid.Row="0"
Fill="{TemplateBinding SelectorColor}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<Path.Data>
<MultiBinding Converter="{StaticResource selectorUpShapeConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Data>
</Path>
<Path x:Name="pathSelectorDown1" Grid.Row="2"
Fill="{TemplateBinding SelectorColor}"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<Path.Data>
<MultiBinding Converter="{StaticResource selectorDownShapeConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Data>
</Path>
</Grid> <Grid x:Name="PART_RangeSelector2" Panel.ZIndex="0"
Canvas.Left="0" Canvas.Top="0" Background="Transparent"
Width="{TemplateBinding SelectorWidth, Converter={StaticResource doubleToGridLengthConverter}}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="{TemplateBinding RangeBarHeight, Converter={StaticResource doubleToGridLengthConverter}}"/>
<RowDefinition/>
</Grid.RowDefinitions> <Path x:Name="pathSelectorUp2" Grid.Row="0"
Fill="{TemplateBinding SelectorColor}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<Path.Data>
<MultiBinding Converter="{StaticResource selectorUpShapeConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Data>
</Path>
<Path x:Name="pathSelectorDown2" Grid.Row="2"
Fill="{TemplateBinding SelectorColor}"
HorizontalAlignment="Center"
VerticalAlignment="Top">
<Path.Data>
<MultiBinding Converter="{StaticResource selectorDownShapeConverter}">
<Binding Path="SelectorWidth" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
<Binding Path="SelectorHeight" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type local:RangeSelector}}"/>
</MultiBinding>
</Path.Data>
</Path>
</Grid> <TextBlock x:Name="PART_LowerMessageTextBlock" TextTrimming="CharacterEllipsis"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{TemplateBinding MessageForeground}"
MaxWidth="{TemplateBinding MessageWidth}" Panel.ZIndex="2"/> <TextBlock x:Name="PART_UpperMessageTextBlock" TextTrimming="CharacterEllipsis"
HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{TemplateBinding MessageForeground}"
MaxWidth="{TemplateBinding MessageWidth}" Panel.ZIndex="2"/> </Canvas>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

       四 逻辑控制

       1 控件的定义与查找

       在类的定义中,我们定义了需要操作所有控件,并在复写OnApplyTemplate方法的时候通过GetTemplateChild方法从模板中找到对应的控件,完成控件的初始化与事件方法注册。如下:

        private Canvas _canvas = null;
private FrameworkElement _rangeElement = null;
private FrameworkElement _rangeSelector1 = null;
private FrameworkElement _rangeSelector2 = null;
private FrameworkElement _selectedRangeElement = null;
private TextBlock _ttbLowerMessage = null;
private TextBlock _ttbUpperMessage = null;
        public override void OnApplyTemplate()
{
base.OnApplyTemplate(); _canvas = GetTemplateChild("PART_Canvas") as Canvas; _rangeElement = GetTemplateChild("PART_Range") as FrameworkElement;
if (_rangeElement != null)
{
_rangeElement.SizeChanged += Control_SizeChanged;
string pathString = string.Format("M{0},0 {1},0 {2},{3} {4},{5}z", 0, 100, 100, RangeBarHeight, 0, RangeBarHeight);
GeometryConverter gc = new GeometryConverter();
(_rangeElement as Path).Data = (Geometry)gc.ConvertFromString(pathString);
} _selectedRangeElement = GetTemplateChild("PART_SelectedRange") as FrameworkElement; _rangeSelector1 = GetTemplateChild("PART_RangeSelector1") as FrameworkElement;
if (_rangeSelector1 != null)
{
_rangeSelector1.SizeChanged += Control_SizeChanged;
_rangeSelector1.MouseLeftButtonDown += Selector_MouseLeftButtonDown;
_rangeSelector1.MouseMove += Selector_MouseMove;
_rangeSelector1.MouseLeftButtonUp += Selector_MouseLeftButtonUp;
} _rangeSelector2 = GetTemplateChild("PART_RangeSelector2") as FrameworkElement;
if (_rangeSelector2 != null)
{
_rangeSelector2.MouseLeftButtonDown += Selector_MouseLeftButtonDown;
_rangeSelector2.MouseMove += Selector_MouseMove;
_rangeSelector2.MouseLeftButtonUp += Selector_MouseLeftButtonUp;
} _ttbUpperMessage = GetTemplateChild("PART_LowerMessageTextBlock") as TextBlock;
if(_ttbUpperMessage != null)
{
Canvas.SetTop(_ttbUpperMessage, SelectorHeight);
} _ttbLowerMessage = GetTemplateChild("PART_UpperMessageTextBlock") as TextBlock;
if(_ttbLowerMessage != null)
{
Canvas.SetTop(_ttbLowerMessage, SelectorHeight);
} InitData();
}

       2 定义依赖属性和属性包装器

       这些依赖属性主要用于与选择器的选择信息(上下界)和展示信息(控件各部分画刷)相关的数据绑定。

        #region Dependency Properties

        public static readonly DependencyProperty UpperBoundaryValueProperty
= DependencyProperty.Register("UpperBoundaryValue", typeof(double), typeof(RangeSelector), new PropertyMetadata(0.0, OnUpBoundaryPropertyChanged));
public static readonly DependencyProperty LowerBoundaryValueProperty
= DependencyProperty.Register("LowerBoundaryValue", typeof(double), typeof(RangeSelector), new PropertyMetadata(0.0, OnLowBoundaryPropertyChanged));
public static readonly DependencyProperty RangeColorProperty
= DependencyProperty.Register("RangeColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Transparent), null));
public static readonly DependencyProperty SelectedRangeColorProperty
= DependencyProperty.Register("SelectedRangeColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.White), null));
public static readonly DependencyProperty SelectorColorProperty
= DependencyProperty.Register("SelectorColor", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Blue), null));
public static readonly DependencyProperty MessageForegroundProperty
= DependencyProperty.Register("MessageForeground", typeof(Brush), typeof(RangeSelector), new PropertyMetadata(new SolidColorBrush(Colors.Black), null));
public static readonly DependencyProperty MessageWidthProperty
= DependencyProperty.Register("MessageWidth", typeof(double), typeof(RangeSelector), new PropertyMetadata(100.0, null));
public static readonly DependencyProperty RangeBarHeightProperty
= DependencyProperty.Register("RangeBarHeight", typeof(double), typeof(RangeSelector), new PropertyMetadata(15.0, null));
public static readonly DependencyProperty SelectorWidthProperty
= DependencyProperty.Register("SelectorWidth", typeof(double), typeof(RangeSelector), new PropertyMetadata(14.0, null));
public static readonly DependencyProperty SelectorHeightProperty
= DependencyProperty.Register("SelectorHeight", typeof(double), typeof(RangeSelector), new PropertyMetadata(25.0, null)); #endregion #region Wrappers public double UpperBoundaryValue
{
get { return (double)GetValue(UpperBoundaryValueProperty); }
set { SetValue(UpperBoundaryValueProperty, value); }
} public double LowerBoundaryValue
{
get { return (double)GetValue(LowerBoundaryValueProperty); }
set { SetValue(LowerBoundaryValueProperty, value); }
} public Brush RangeColor
{
get { return (Brush)GetValue(RangeColorProperty); }
set { SetValue(RangeColorProperty, value); }
} public Brush SelectedRangeColor
{
get { return (Brush)GetValue(SelectedRangeColorProperty); }
set { SetValue(SelectedRangeColorProperty, value); }
} public Brush SelectorColor
{
get { return (Brush)GetValue(SelectorColorProperty); }
set { SetValue(SelectorColorProperty, value); }
} public Brush MessageForeground
{
get { return (Brush)GetValue(MessageForegroundProperty); }
set { SetValue(MessageForegroundProperty, value); }
} public double MessageWidth
{
get { return (double)GetValue(MessageWidthProperty); }
set { SetValue(MessageWidthProperty, value); }
} public double RangeBarHeight
{
get { return (double)GetValue(RangeBarHeightProperty); }
set { SetValue(RangeBarHeightProperty, value); }
} public double SelectorWidth
{
get { return (double)GetValue(SelectorWidthProperty); }
set { SetValue(SelectorWidthProperty, value); }
} public double SelectorHeight
{
get { return (double)GetValue(SelectorHeightProperty); }
set { SetValue(SelectorHeightProperty, value); }
}

       3 定义鼠标拖动事件方法

       在鼠标拖动的过程中,除了要对选择器控件进行移动外,还要实时更新选择的范围数据以及选择的展示信息。具体的在UpdateSelectedRange方法以及UpdateShownMessage方法中执行。

        /// <summary>
/// 鼠标按下
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Selector_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (_canvas == null)
{
return;
} FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
} //创建鼠标捕获
Mouse.Capture(element);
_enableMove = true;
_spanLeft = e.GetPosition(_canvas).X - Canvas.GetLeft(element);
} /// <summary>
/// 鼠标移动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Selector_MouseMove(object sender, MouseEventArgs e)
{
if (_canvas == null)
{
return;
} FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
} if (_enableMove)
{
double cLeft = e.GetPosition(_canvas).X - _spanLeft;
if (double.IsNaN(cLeft))
{
cLeft = 0;
} //边界限制
if (cLeft > _upperBound)
{
cLeft = _upperBound;
}
else if (cLeft < _lowerBound)
{
cLeft = _lowerBound;
} //设置元素的位置
Canvas.SetLeft(element, cLeft); //更新选择图像
UpdateSelectedRange();
//更新提示信息
UpdateShownMessage();
}
} /// <summary>
/// 鼠标松开
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Selector_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element == null)
{
return;
} //释放鼠标捕获
element.ReleaseMouseCapture();
_enableMove = false;
_spanLeft = 0;
}
        /// <summary>
/// 更新选择范围
/// </summary>
private void UpdateSelectedRange()
{
if (_selectedRangeElement == null || _range == 0)
{
return;
} GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector);
if (lowerSelector == null || upperSelector == null)
{
return;
} double lower = Canvas.GetLeft(lowerSelector);
double upper = Canvas.GetLeft(upperSelector);
if (double.IsNaN(lower) || double.IsNaN(upper))
{
return;
} string pathString = string.Format("M{0},0 {1},0 {2},{3} {4},{5}z", lower, upper, upper, RangeBarHeight, lower, RangeBarHeight);
GeometryConverter gc = new GeometryConverter();
(_selectedRangeElement as Path).Data = (Geometry)gc.ConvertFromString(pathString);
Canvas.SetLeft(_selectedRangeElement, lower); UpperBoundaryValue = upper / _range;
LowerBoundaryValue = lower / _range; if (_ttbLowerMessage != null)
{
Canvas.SetLeft(_ttbLowerMessage, lower - SelectorWidth / 2 - _ttbLowerMessage.ActualWidth);
} if (_ttbUpperMessage != null)
{
Canvas.SetLeft(_ttbUpperMessage, upper + SelectorWidth * 3 / 2);
}
} /// <summary>
/// 更新选择器的显示信息
/// </summary>
private void UpdateShownMessage()
{
if (ConvertRangeToMessage == null || _range == 0
|| _ttbUpperMessage == null || _ttbLowerMessage == null)
{
return;
} GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector);
if (lowerSelector == null || upperSelector == null)
{
return;
} double lowerSelectorCanvasLeft = Canvas.GetLeft(lowerSelector);
double upperSelectorCanvasLeft = Canvas.GetLeft(upperSelector);
if (double.IsNaN(lowerSelectorCanvasLeft) || double.IsNaN(upperSelectorCanvasLeft))
{
return;
} double upperValue = upperSelectorCanvasLeft / _range;
double lowerValue = lowerSelectorCanvasLeft / _range; string upperMessage = ConvertRangeToMessage(upperValue);
string lowerMessage = ConvertRangeToMessage(lowerValue); _ttbUpperMessage.Text = upperMessage;
_ttbUpperMessage.ToolTip = upperMessage;
_ttbLowerMessage.Text = lowerMessage;
_ttbLowerMessage.ToolTip = lowerMessage;
}

       另外,在鼠标拖动选择器的过程中,并不限制某个选择器会在左边还是右边,因此会用下面的方法实时的分辨左右选择器。

        /// <summary>
/// 判断两个选择器中哪一个是上界选择器,哪一个是下界选择器
/// </summary>
/// <param name="lowerSelector"></param>
/// <param name="upperSelector"></param>
private void GetUpperLowerSelector(out FrameworkElement lowerSelector, out FrameworkElement upperSelector)
{
if (_rangeSelector1 == null || _rangeSelector2 == null)
{
lowerSelector = null;
upperSelector = null;
return;
} if (Canvas.GetLeft(_rangeSelector1) < Canvas.GetLeft(_rangeSelector2))
{
lowerSelector = _rangeSelector1;
upperSelector = _rangeSelector2;
}
else
{
lowerSelector = _rangeSelector2;
upperSelector = _rangeSelector1;
}
}

       4 选择信息的计算与展示

       在移动选择器的过程中,控件会根据上下界选择器在整个选择范围的位置计算其归一化的值,并赋值给依赖属性,以便将此选择范围暴露给使用者。但是,显示选择信息的时候,我们可以从外界传递一个委托方法,将选择器的范围值转化成格式化的字符串。以便在TextBlock上显示。

       五 控件的应用

       在使用控件的地方,为控件绑定好相关的属性就能获取到控件选择范围的上下界了。同时,因为控件内部的一些颜色属性通过依赖属性暴露了出来,所以可以在使用的地方灵活的更改控件各部分的颜色,以便得到想要的效果。

<Window x:Class="Test.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:Test"
xmlns:rs="clr-namespace:RangeSelectors;assembly=RangeSelectors"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Background="#ffffff">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition/>
</Grid.RowDefinitions>
<rs:RangeSelector x:Name="rsTest" Margin="20"
Background="Transparent"
SelectedRangeColor="#ff0000"
MessageForeground ="#ffffff"
BorderBrush="Red"
BorderThickness="0.5"
RangeBarHeight="15"
SelectorHeight="25"
SelectorWidth="12" RenderTransformOrigin="0.5,0.5">
<rs:RangeSelector.RangeColor>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#ffffff" Offset="0.0" />
<GradientStop Color="#00ff00" Offset="0.8" />
<GradientStop Color="#009900" Offset="1" />
</LinearGradientBrush>
</rs:RangeSelector.RangeColor> </rs:RangeSelector> <StackPanel Orientation="Horizontal" Grid.Row="1">
<TextBox Text="{Binding LowerBoundaryValue, ElementName=rsTest, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Margin="20"/>
<TextBox Text="{Binding UpperBoundaryValue, ElementName=rsTest, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Foreground="Black" Margin="20"/>
</StackPanel>
</Grid>
</Window>

  public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
rsTest.ConvertRangeToMessage = new Func<double, string>((d) =>
{
DateTime recordStartTime = DateTime.Now;
DateTime recordEndTime = DateTime.Now.AddDays(1); double recordLengthInSecond = (recordEndTime - recordStartTime).TotalSeconds;
DateTime selectedTime = recordStartTime.AddSeconds(recordLengthInSecond * d); return selectedTime.ToString("HH:mm:ss");
});
}
}

       效果图:

       

       源代码

WPF范围选择控件(RangeSelector)的更多相关文章

  1. 潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据

    原文:潜移默化学会WPF(难点控件treeview)--改造TreeView(CheckBox多选择版本),递归绑定数据 目前自己对treeview的感慨很多 今天先讲 面对这种 表结构的数据 的其中 ...

  2. 【C#】wpf自定义calendar日期选择控件的样式

    原文:[C#]wpf自定义calendar日期选择控件的样式 首先上图看下样式 原理 总览 ItemsControl内容的生成 实现 界面的实现 后台ViewModel的实现 首先上图,看下样式 原理 ...

  3. wpf timePicker 时间选择控件

    wpf里有日期选择控件,但没有时间选择控件.其他地方也有类似的,但效果并不太好,而且复杂.所以就自己写了个.参考codeproject上的. 分两部分. 第一部分是.cs文件.也就是control控件 ...

  4. 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步

    深入理解MVC   MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...

  5. WPF 4 DataGrid 控件(进阶篇一)

    原文:WPF 4 DataGrid 控件(进阶篇一)      上一篇<WPF 4 DataGrid 控件(自定义样式篇)>中,我们掌握了DataGrid 列表头.行表头.行.单元格相关的 ...

  6. WPF 4 DataGrid 控件(基本功能篇)

    原文:WPF 4 DataGrid 控件(基本功能篇)      提到DataGrid 不管是网页还是应用程序开发都会频繁使用.通过它我们可以灵活的在行与列间显示各种数据.本篇将详细介绍WPF 4 中 ...

  7. WPF 4 日历控件(Calendar)

    原文:WPF 4 日历控件(Calendar)      在之前我已经写过两篇关于WPF 4 任务栏(Taskbar)相关的特性.相信自从VS2010 Beta 版放出后,WPF 的粉丝们肯定在第一时 ...

  8. Windows Community Toolkit 3.0 新功能 在WinForms 和 WPF 使用 UWP 控件

    本文告诉大家一个令人震惊的消息,Windows Community Toolkit 有一个大更新,现在的版本是 3.0 .最大的提升就是 WinForm 和 WPF 程序可以使用部分 UWP 控件. ...

  9. WPF中Ribbon控件的使用

    这篇博客将分享如何在WPF程序中使用Ribbon控件.Ribbon可以很大的提高软件的便捷性. 上面截图使Outlook 2010的界面,在Home标签页中,将所属的Menu都平铺的布局,非常容易的可 ...

随机推荐

  1. swift学习第十四天:属性监听器

    监听属性的改变 在OC中我们可以重写set方法来监听属性的改变 Swift中可以通过属性观察者来监听和响应属性值的变化 通常是监听存储属性和类属性的改变.(对于计算属性,我们不需要定义属性观察者,因为 ...

  2. 关于JavaScript对象概念的总结

    原文 https://www.jianshu.com/p/88213b499c4b 大纲 前言 1.对象的相关概念 2.对象的创建(简单创建) 3.对象的属性 3.1.数据属性 3.2.访问器属性 4 ...

  3. 向 Windows 高级用户进阶,这 10 款效率工具帮你开路 | 新手问号

    原文地址:https://sspai.com/post/41411 编注:「新手问号」是少数派的一个全新栏目.它面向完全「零基础」的新手用户,通过最简单易懂的方式,帮助你快速掌握关于系统和软硬件的入门 ...

  4. 网易前端微专业,JavaScript程序设计基础篇:数组

    不论什么一种语言数组都是比較重要的,其作为一种基础对象应用非常多,如Java你肯定少不了集合(List,Map)这些.因此本篇主要记录JS的数组使用和经常用法.要点例如以下: 1,数组创建 两种方式: ...

  5. 1. java.util.concurrent - Java 并发工具包

    1. java.util.concurrent - Java 并发工具包 Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包.这个包包含有一系列能够让 Ja ...

  6. php求和为s的两个数字(多复制上面写的代码,有利于检查错误)(由浅入深,先写简单算法,做题的话够用就行)

    php求和为s的两个数字(多复制上面写的代码,有利于检查错误)(由浅入深,先写简单算法,做题的话够用就行) 一.总结 1.多复制上面写的代码,有利于检查错误 2.一层循环就解决了,前后两个指针,和大了 ...

  7. 小强的HTML5移动开发之路(52)——jquerymobile中的触控交互

    当使用移动设备进行触控操作时,最常用的就是轻击.按住屏幕或者手势操作,jQuery Mobile可以通过绑定的触控事件来响应使用者的特定触控行为. 一.轻击与按住 直接上代码(一切皆在代码中,细细品吧 ...

  8. 【u015】兽径管理

    [问题描述] 约翰农场的牛群希望能够在 N 个(1<=N<=200)草地之间任意移动.草地的编号由 1到N.草地之间有树林隔开.牛群希望能够选择草地间的路径,使牛群能够从任一 片草地移动到 ...

  9. 【27.22%】【poj2991】Crane

    Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 5772   Accepted: 1571   Special Judge D ...

  10. Visual Studio for Mac

    Visual Studio for Mac 初体验   你喜爱的 IDE,现在可用于 Mac 来自:https://www.visualstudio.com/zh-hans/vs/visual-stu ...