继之前那个控件,又做了一个原理差不多的控件。这个控件主要模仿百度贴吧WP版帖子浏览界面左下角那个弹出的按钮盘。希望对大家有帮助。

这个控件和之前的也差不多,为了不让大家白看,文章最后发干货。

由于这个控件和之前一篇文章介绍控件基本差不多,所以一些基本的实现点不再赘述,文本将主要介绍与这个控件功能密切相关的部分。开始正题。

剧透一下,博主后来又在WinRT(真不知道该叫什么好了,现在该叫它UWP吗?)中把这个控件实现了一遍,说起来WinRT与WPF还是有很大不同的,这个控件的实现方式也有很多不同之处。后续的文章将会有介绍。

按惯例先上最终效果图:

弹出的子菜单可以点击,用于自定义需要点击子菜单实现的功能:

首先还是先来展示一下控件模板的基本结构:

基本上分为四部分:定义状态,定义中间的大按钮,圆形透明背景,以及显示一圈小按钮的Panel。

大按钮和圆形透明背景很简单:

<Border Panel.ZIndex="999" x:Name="PART_CenterBtn" VerticalAlignment="Center" HorizontalAlignment="Center"
            Width="50" Height="50" CornerRadius="25" BorderThickness="0" BorderBrush="Blue" Background="CadetBlue">
    <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="读书"></TextBlock>
</Border> <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Panel.ZIndex="-1" Fill="#66559977"></Ellipse>

注意:当前圆形背景不能按设置的角度变成扇形,需要这个功能的童鞋可以自行做一个可以绑定到角度的扇形控件。

最值得关注就是定义的几个状态,子菜单正是根据不同的状态来在收缩和展开模型来回切换。状态定义如下:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="CommonStates">
        <VisualState x:Name="Initial">
            <Storyboard >
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_PanelPresenter"
                                               Storyboard.TargetProperty="Status">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <circleMenu:CircleMenuStatus>Initial</circleMenu:CircleMenuStatus>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Collapsed">
            <Storyboard >
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_PanelPresenter"
                                               Storyboard.TargetProperty="Status">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <circleMenu:CircleMenuStatus>Collapsed</circleMenu:CircleMenuStatus>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
        <VisualState x:Name="Expanded">
            <Storyboard >
                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PART_PanelPresenter" 
                                               Storyboard.TargetProperty="Status">
                    <DiscreteObjectKeyFrame KeyTime="0">
                        <DiscreteObjectKeyFrame.Value>
                            <circleMenu:CircleMenuStatus>Expanded</circleMenu:CircleMenuStatus>
                        </DiscreteObjectKeyFrame.Value>
                    </DiscreteObjectKeyFrame>
                </ObjectAnimationUsingKeyFrames>
            </Storyboard>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

不同状态的切换通过Storyboard中的ObjectAnimationUsingKeyFrames控制PART_Presenter的Status。这个PART_Presenter是我们自定义的继承自ItemPresenter的一个类型的对象。控件以这个自定义的Presenter作为桥梁,外部通过VisualStateManager更改其Status这个依赖属性,而内部自定义的Panel可以绑定到这个Status属性,从而根据当前的状态来对其中的元素进行布局。

先来看看这个自定义的ItemPresenter:

public class CircleMenuItemsPresenter:ItemsPresenter
{
    public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
        "Status", typeof (CircleMenuStatus), typeof (CircleMenuItemsPresenter), new PropertyMetadata(default(CircleMenuStatus)));     public CircleMenuStatus Status
    {
        get { return (CircleMenuStatus) GetValue(StatusProperty); }
        set { SetValue(StatusProperty, value); }
    }     public static readonly DependencyProperty AngleProperty = DependencyProperty.Register(
        "Angle", typeof(Double), typeof(CircleMenuItemsPresenter), new PropertyMetadata(360d));     public double Angle
    {
        get { return (Double)GetValue(AngleProperty); }
        set { SetValue(AngleProperty, value); }
    }
}

很简单就是添加了作为Control和Panel桥梁的几个依赖属性。(最根本的原因还是自定义的Panel不能直接绑定到Control的依赖属性,最多只能绑定到其父级ItemPresenter)

在WinRT中ItemPresenter变成了密封类,我们没法像上面那个自定义一个ItemPresenter供Panel绑定。所以实现方式有了很大变化。以后的文章会细说

接着是ItemPresenter和Panel的声明:

<circleMenu:CircleMenuItemsPresenter x:Name="PART_PanelPresenter" Status="Initial" Angle="{TemplateBinding Angle}" />

<Setter Property="ItemsPanel">
    <Setter.Value>
        <ItemsPanelTemplate>
            <circleMenu:CircleMenuPanel x:Name="CircleMenuPanel" AnimationDuration="{StaticResource CircleDuration}" 
                                                                 AnimationDurationStep="0.2"
                                                                 Radius="100" 
                                        Angle="{Binding Angle, RelativeSource={RelativeSource FindAncestor, AncestorType=circleMenu:CircleMenuItemsPresenter}}" 
                                        PanelStatus="{Binding Status, RelativeSource={RelativeSource FindAncestor, AncestorType=circleMenu:CircleMenuItemsPresenter } }" />
        </ItemsPanelTemplate>
    </Setter.Value>
</Setter>

可以看到,如果想让这个自定的Panel绑定到外部(控件级)的依赖属性就需要通过ItemPresenter中转一下。

接着是整个控件最核心的一部分CircleMenuPanel这个自定义面板的实现,这个文件比较长,分段来看。

首先是一些依赖属性,在上面的XAML它们的身影也出现过一次。

public static readonly DependencyProperty AnimationDurationProperty = DependencyProperty.Register(
            "AnimationDuration", typeof(Duration), typeof(CircleMenuPanel), new PropertyMetadata(default(Duration))); public Duration AnimationDuration
{
    get { return (Duration)GetValue(AnimationDurationProperty); }
    set { SetValue(AnimationDurationProperty, value); }
} public static readonly DependencyProperty AnimationDurationStepProperty = DependencyProperty.Register(
    "AnimationDurationStep", typeof(double), typeof(CircleMenuPanel), new PropertyMetadata(0.3d)); public double AnimationDurationStep
{
    get { return (double)GetValue(AnimationDurationStepProperty); }
    set { SetValue(AnimationDurationStepProperty, value); }
} public static readonly DependencyProperty RadiusProperty = DependencyProperty.Register(
    "Radius", typeof(Double), typeof(CircleMenuPanel), new PropertyMetadata(50d)); public double Radius
{
    get { return (Double)GetValue(RadiusProperty); }
    set { SetValue(RadiusProperty, value); }
} public static readonly DependencyProperty AngleProperty = DependencyProperty.Register(
    "Angle", typeof(double), typeof(CircleMenuPanel), new PropertyMetadata(360d)); public double Angle
{
    get { return (double)GetValue(AngleProperty); }
    set { SetValue(AngleProperty, value); }
} public static readonly DependencyProperty PanelStatusProperty = DependencyProperty.Register(
    "PanelStatus", typeof(CircleMenuStatus), typeof(CircleMenuPanel), new PropertyMetadata(CircleMenuStatus.Initial, ReRender)); public CircleMenuStatus PanelStatus
{
    get { return (CircleMenuStatus)GetValue(PanelStatusProperty); }
    set { SetValue(PanelStatusProperty, value); }
} private static void ReRender(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var circelPanel = (CircleMenuPanel)d;
    circelPanel.InvalidateArrange();
}

值得注意的是,在PanelStatus变化时触发了一个回调函数用来实现在面板(控件)状态变化时的重绘。

接着就是和布局相关的两个方法:

protected override Size MeasureOverride(Size availableSize)
{
    var s = base.MeasureOverride(availableSize);
    foreach (UIElement element in this.Children)
    {
        element.Measure(availableSize);
    }
    return availableSize;
} //http://www.cnblogs.com/mantgh/p/4161142.html
protected override Size ArrangeOverride(Size finalSize)
{
    var cutNum = (int)Angle == 360 ? this.Children.Count : (this.Children.Count - 1);
    var degreesOffset = Angle / cutNum;
    var i = 0;
    foreach (ContentPresenter element in Children)
    {
        var elementRadius = element.DesiredSize.Width / 2.0;
        var elementCenterX = elementRadius;
        var elementCenterY = elementRadius;         var panelCenterX = Radius - elementRadius;
        var panelCenterY = Radius - elementRadius;         var degreesAngle = degreesOffset * i;
        var radianAngle = (Math.PI * degreesAngle) / 180.0;
        var x = this.Radius * Math.Sin(radianAngle);
        var y = -this.Radius * Math.Cos(radianAngle);
        var destX = x + finalSize.Width / 2 - elementCenterX;
        var destY = y + finalSize.Height / 2 - elementCenterY;         switch (PanelStatus)
        {
            case CircleMenuStatus.Initial:
                ArrangeInitialElement(element, panelCenterX, panelCenterY);
                break;
            case CircleMenuStatus.Collapsed:
                ArrangeCollapseElement(i, element, panelCenterX, panelCenterY, elementCenterX, elementCenterY, destX, destY);
                break;
            case CircleMenuStatus.Expanded:
                ArrangeExpandElement(i, element, panelCenterX, panelCenterY, elementCenterX, elementCenterY, destX, destY);
                break;
        }         ++i;
    }
    return finalSize;
}

当然重点是在ArrangeOverride方法中,针对每个元素的操作先是经过一些列计算得到分布在圆周上的位置,然后根据面板状态分别调用3个方法进行实际位置布局。如果是初始状态,只需要放置于中点就可以了。如果是Collapsed,则将子元素由圆周移动回中心。反之如果是Expanded则将小球由中心逐渐移动到圆周。

三个实际布局方法见下:

private void ArrangeExpandElement(int idx, ContentPresenter element,
    double panelCenterX, double panelCenterY,
    double elementCenterX, double elementCenterY,
    double destX, double destY)
{
    element.Arrange(new Rect(panelCenterX, panelCenterY, element.DesiredSize.Width, element.DesiredSize.Height));     var transGroup = element.RenderTransform as TransformGroup;
    Transform translateTransform, rotateTransform;
    if (transGroup == null)
    {
        element.RenderTransform = transGroup = new TransformGroup();
        translateTransform = new TranslateTransform();
        rotateTransform = new RotateTransform() { CenterX = elementCenterX, CenterY = elementCenterY };         transGroup.Children.Add(translateTransform);
        transGroup.Children.Add(rotateTransform);
    }
    else
    {
        translateTransform = transGroup.Children[0] as TranslateTransform;
        rotateTransform = transGroup.Children[1] as RotateTransform;
    }
    element.RenderTransformOrigin = new Point(0.5, 0.5);     //if (i != 0) continue;     var aniDuration = AnimationDuration + TimeSpan.FromSeconds(AnimationDurationStep * idx);
    translateTransform.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(0, destX - panelCenterX, aniDuration));
    translateTransform.BeginAnimation(TranslateTransform.YProperty, new DoubleAnimation(0, destY - panelCenterY, aniDuration));     rotateTransform.BeginAnimation(RotateTransform.CenterXProperty, new DoubleAnimation(0, destX - panelCenterX, aniDuration));
    rotateTransform.BeginAnimation(RotateTransform.CenterYProperty, new DoubleAnimation(0, destY - panelCenterY, aniDuration));
    rotateTransform.BeginAnimation(RotateTransform.AngleProperty, new DoubleAnimation(0, 720, aniDuration));     element.BeginAnimation(OpacityProperty, new DoubleAnimation(0.2, 1, aniDuration));
} private void ArrangeInitialElement(ContentPresenter element, double panelCenterX, double panelCenterY)
{
    element.Arrange(new Rect(panelCenterX, panelCenterY, element.DesiredSize.Width, element.DesiredSize.Height));
} private void ArrangeCollapseElement(int idx, ContentPresenter element,
                double panelCenterX, double panelCenterY,
                double elementCenterX, double elementCenterY,
                double destX, double destY)
{
    element.Arrange(new Rect(destX, destY, element.DesiredSize.Width, element.DesiredSize.Height));     var transGroup = element.RenderTransform as TransformGroup;
    Transform translateTransform, rotateTransform;
    if (transGroup == null)
    {
        element.RenderTransform = transGroup = new TransformGroup();
        translateTransform = new TranslateTransform();
        rotateTransform = new RotateTransform() { CenterX = elementCenterX, CenterY = elementCenterY };         transGroup.Children.Add(translateTransform);
        transGroup.Children.Add(rotateTransform);
    }
    else
    {
        translateTransform = transGroup.Children[0] as TranslateTransform;
        rotateTransform = transGroup.Children[1] as RotateTransform;
    }
    element.RenderTransformOrigin = new Point(0.5, 0.5);     //if (i != 0) continue;     var aniDuration = AnimationDuration + TimeSpan.FromSeconds(AnimationDurationStep * idx);
    translateTransform.BeginAnimation(TranslateTransform.XProperty, new DoubleAnimation(0, panelCenterX - destX, aniDuration));
    translateTransform.BeginAnimation(TranslateTransform.YProperty, new DoubleAnimation(0, panelCenterY - destY, aniDuration));     rotateTransform.BeginAnimation(RotateTransform.CenterXProperty, new DoubleAnimation(0, panelCenterX - destX, aniDuration));
    rotateTransform.BeginAnimation(RotateTransform.CenterYProperty, new DoubleAnimation(0, panelCenterY - destY, aniDuration));
    rotateTransform.BeginAnimation(RotateTransform.AngleProperty, new DoubleAnimation(0, -720, aniDuration));     element.BeginAnimation(OpacityProperty, new DoubleAnimation(1, 0.2, aniDuration));
}

透明动画是直接给子元素的Opacity属性施加了动画效果,而移动和旋转先组合为一个TransformGroup然后应用给子元素的RenderTransform。代码很容易懂,实现的时候注意下TranslateTransform的起至点坐标的计算和RotateTransform变化的旋转中心点的即可。特别是这个旋转中心点,其随着子元素“移动”过程也在不停的变化,从而使子元素总是相对于“当前”的中心在旋转。

到这里剩下的都比较简单了:

控件的代码:

    [TemplatePart(Name = PartCenterBtn)]
    [TemplatePart(Name = PartContainer)]
    [TemplatePart(Name = PartPanelPresenter)]
    [TemplateVisualState(GroupName = "CommonStates", Name = VisualStateInitial)]
    [TemplateVisualState(GroupName = "CommonStates", Name = VisualStateExpanded)]
    [TemplateVisualState(GroupName = "CommonStates", Name = VisualStateCollapsed)]
    public class CircleMenuControl : ItemsControl
    {
        private const string PartCenterBtn = "PART_CenterBtn";
        private const string PartContainer = "PART_Container";
        private const string PartPanelPresenter = "PART_PanelPresenter";
        public const string VisualStateInitial = "Initial";
        public const string VisualStateExpanded = "Expanded";
        public const string VisualStateCollapsed = "Collapsed";         static CircleMenuControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CircleMenuControl), new FrameworkPropertyMetadata(typeof(CircleMenuControl)));
        }         #region dependency property         public static readonly DependencyProperty AngleProperty = DependencyProperty.Register(
            "Angle", typeof(double), typeof(CircleMenuControl), new PropertyMetadata(360d));         public double Angle
        {
            get { return (double)GetValue(AngleProperty); }
            set { SetValue(AngleProperty, value); }
        }         #endregion         private Border _centerBtn;
        private Grid _container;
        private CircleMenuPanel _circleMenuPanel;
        private CircleMenuItemsPresenter _circleMenuItemsPresenter;         public override void OnApplyTemplate()
        {
            if (_centerBtn != null)
            {
                _centerBtn.MouseLeftButtonUp -= centerBtn_Click;
            }             base.OnApplyTemplate();             _centerBtn = GetTemplateChild(PartCenterBtn) as Border;
            _container = GetTemplateChild(PartContainer) as Grid;
            _circleMenuItemsPresenter = GetTemplateChild(PartPanelPresenter) as CircleMenuItemsPresenter;             if (_centerBtn != null)
            {
                _centerBtn.MouseLeftButtonUp += centerBtn_Click;
            }
        }         private void centerBtn_Click(object sender, RoutedEventArgs e)
        {
            //第一个参数是<VisualStateManager>所在元素的父元素,本控件中为Grid的父级,即控件本身
            switch (_circleMenuItemsPresenter.Status)
            {
                case CircleMenuStatus.Expanded:
                    VisualStateManager.GoToState(this, VisualStateCollapsed, false);
                    break;
                case CircleMenuStatus.Initial:
                case CircleMenuStatus.Collapsed:
                    VisualStateManager.GoToState(this, VisualStateExpanded, false);
                    break;
            }             //如果只是在控件内部更改Panel状态可以直接设置ItemPresenter的Status
            //使用VisualStateManager是为了可以在外部通过更改状态更新面板
        }         #region route event         //inner menu click
        public static readonly RoutedEvent SubMenuClickEvent =
            ButtonBase.ClickEvent.AddOwner(typeof (CircleMenuControl));         public event RoutedEventHandler SubMenuClick
        {
            add { AddHandler(ButtonBase.ClickEvent, value, false); }
            remove { RemoveHandler(ButtonBase.ClickEvent, value); }
        }         #endregion     }

可以看到仍然是从ItemsControl集成来的控件。

这几行声明模板支持状态的代码可以告诉自定义控件模板的用户可以在模板中定义哪几种VisualState:

[TemplateVisualState(GroupName = "CommonStates", Name = VisualStateInitial)]
[TemplateVisualState(GroupName = "CommonStates", Name = VisualStateExpanded)]
[TemplateVisualState(GroupName = "CommonStates", Name = VisualStateCollapsed)]

在中央按钮被点击的时候调用VisualStateManger.GoToState来切换控件状态。

private void centerBtn_Click(object sender, RoutedEventArgs e)
{
    //第一个参数是<VisualStateManager>所在元素的父元素,本控件中为Grid的父级,即控件本身
    switch (_circleMenuItemsPresenter.Status)
    {
        case CircleMenuStatus.Expanded:
            VisualStateManager.GoToState(this, VisualStateCollapsed, false);
            break;
        case CircleMenuStatus.Initial:
        case CircleMenuStatus.Collapsed:
            VisualStateManager.GoToState(this, VisualStateExpanded, false);
            break;
    }
}

而子元素点击事件的发布和之前的控件处理方式差不多。

这里在控件中定义一个路由事件,处理子控件中没有被处理的Button.Click事件(这里选用了简单的实现方式限制子元素为Button):

#region route event

//inner menu click
public static readonly RoutedEvent SubMenuClickEvent =
    ButtonBase.ClickEvent.AddOwner(typeof (CircleMenuControl)); public event RoutedEventHandler SubMenuClick
{
    add { AddHandler(ButtonBase.ClickEvent, value, false); }
    remove { RemoveHandler(ButtonBase.ClickEvent, value); }
} #endregion

从控件使用的代码可以看到怎么订阅这个事件:

<circleMenu:CircleMenuControl ItemsSource="{Binding SubMenuItems}" Width="200" Height="200"
                              BorderThickness="2" BorderBrush="Black">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SubMenuClick">
            <command:EventToCommand Command="{Binding NodeClickCommand, Mode=OneWay}" PassEventArgsToCommand="True" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <circleMenu:CircleMenuControl.ItemTemplate>
        <DataTemplate>
            <Button>
                <Button.Template>
                    <ControlTemplate TargetType="Button">
                        <Border CornerRadius="15" Background="Coral" Width="30" Height="30" >
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
                        </Border>
                    </ControlTemplate>
                </Button.Template>
                <Button.Content>
                        <TextBlock Text="{Binding Title}"></TextBlock>
                </Button.Content>
            </Button>
            
        </DataTemplate>
    </circleMenu:CircleMenuControl.ItemTemplate>
</circleMenu:CircleMenuControl>

Item模板是一个自定义模板的Button,其未处理的事件会被向上传递触发控件的SubMenuClick事件。订阅事件还是借助MVVM Light中的EventToCommand这个方便的标签。事件处理Command:

private RelayCommand<RoutedEventArgs> _nodeClickCommand;

public RelayCommand<RoutedEventArgs> NodeClickCommand
{
    get
    {
        return _nodeClickCommand
            ?? (_nodeClickCommand = new RelayCommand<RoutedEventArgs>(
                                  p =>
                                  {
                                      var dataItem = ((FrameworkElement)p.OriginalSource).DataContext;
                                      MessageBox.Show(((CircleMenuItem)dataItem).Id.ToString());                                       var circleCtrl = (CircleMenuControl)p.Source;
                                      var suc = VisualStateManager.GoToState(circleCtrl, CircleMenuControl.VisualStateCollapsed, false);
                                      var bb = 1;
                                  }));
    }
}

最后为了完整性,把子元素用到的实体和绑定Items列表的代码也列到下面。这些和之前控件所介绍的基本一致。

public class CircleMenuItem
{
    public CircleMenuItem()
    {
    }     public CircleMenuItem(int id, string title,double offsetRate)
    {
        Id = id;
        Title = title;
    }     public int Id { get; set; }     public string Title { get; set; }
}
//ViewModel
_dataService.GetData(
    (item, error) =>
    {
        SubMenuItems = new ObservableCollection<CircleMenuItem>(
            new List<CircleMenuItem>()
            {
                new CircleMenuItem() {Id = 1, Title = "衣"},
                new CircleMenuItem() {Id = 2, Title = "带"},
                new CircleMenuItem() {Id = 3, Title = "渐"},
                new CircleMenuItem() {Id = 4, Title = "宽"},
                new CircleMenuItem() {Id = 5, Title = "终"},
                new CircleMenuItem() {Id = 6, Title = "不"},
                new CircleMenuItem() {Id = 7, Title = "悔"},
                new CircleMenuItem() {Id = 8, Title = "为"},
                new CircleMenuItem() {Id = 9, Title = "伊"},
                new CircleMenuItem() {Id = 10, Title = "消"},
                new CircleMenuItem() {Id = 11, Title = "得"},
                new CircleMenuItem() {Id = 12, Title = "人"},
                new CircleMenuItem() {Id = 13, Title = "憔"},
                new CircleMenuItem() {Id = 14, Title = "悴"}
            });
    }); private ObservableCollection<CircleMenuItem> _subMenuItems; public ObservableCollection<CircleMenuItem> SubMenuItems
{
    get { return _subMenuItems; }
    set { Set(() => SubMenuItems, ref _subMenuItems, value); }
}

基本上这个控件就是这样,大家多给意见。下面是干活

其他干货

在很长一段学习使用XAML系开发平台的过程中,逐步整理完善了一份Xmind文件,发出来供大家使用。像WPF系结构复杂,如果忘了什么可以看一个这个文档参考,可以省不少时间。

先上几张图,后面有下载地址

图1

图2

图3

下载地址

代码下载

Github

版权说明:本文版权归博客园和hystar所有,转载请保留本文地址。文章代码可以在项目随意使用,如果以文章出版物形式出现请表明来源,尤其对于博主引用的代码请保留其中的原出处尊重原作者劳动成果。

WPF自定义控件第二 - 转盘按钮控件的更多相关文章

  1. WinRT自定义控件第一 - 转盘按钮控件

    之前的文章中,介绍了用WPF做一个转盘按钮控件,后来需要把这个控件移植到WinRT时,遇到了很大的问题,主要原因在于WPF和WinRT还是有很大不同的.这篇文章介绍了这个移植过程,由于2次实现的控件功 ...

  2. WPF自定义控件(一)の控件分类

    一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...

  3. Qt编写自定义控件9-导航按钮控件

    前言 导航按钮控件,主要用于各种漂亮精美的导航条,我们经常在web中看到导航条都非常精美,都是html+css+js实现的,还自带动画过度效果,Qt提供的qss其实也是无敌的,支持基本上所有的CSS2 ...

  4. WPF自定义控件第一 - 进度条控件

    本文主要针对WPF新手,高手可以直接忽略,更希望高手们能给出一些更好的实现思路. 前期一个小任务需要实现一个类似含步骤进度条的控件.虽然对于XAML的了解还不是足够深入,还是摸索着做了一个.这篇文章介 ...

  5. WPF自定义控件一:StackPanel 控件轮播

    实现效果 带定时器的轮播图 using引用 using System.Windows; using System.Windows.Controls; using System.Windows.Mark ...

  6. 【WPF学习】第二十章 内容控件

    内容控件(content control)是更特殊的控件类型,它们可包含并显示一块内容.从技术角度看,内容控件时可以包含单个嵌套元素的控件.与布局容器不同的是,内容控件只能包含一个子元素,而布局容器主 ...

  7. Qt编写自定义控件11-设备防区按钮控件

    前言 在很多项目应用中,需要根据数据动态生成对象显示在地图上,比如地图标注,同时还需要可拖动对象到指定位置显示,能有多种状态指示,安防领域一般用来表示防区或者设备,可以直接显示防区号,有多种状态颜色指 ...

  8. Android自定义控件之自定义组合控件

    前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发 ...

  9. C#自定义Button按钮控件

    C#自定义Button按钮控件 在实际项目开发中经常可以遇到.net自带控件并不一定可以满足需要,因此需要自定义开发一些新的控件,自定义控件的办法也有多种,可以自己绘制线条颜色图形等进行重绘,也可以采 ...

随机推荐

  1. Fis3的前端模块化之路[基础篇]

    Fis3版本:v3.4.22 fis3是一个构建工具 解决前端开发中自动化工具.性能优化.模块化框架.开发规范.代码部署.开发流程等问题. 安装 npm install -g fis3 运行 fis3 ...

  2. MVVM设计模式和WPF中的实现(四)事件绑定

    MVVM设计模式和在WPF中的实现(四) 事件绑定 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在WPF中 ...

  3. Javascript实用方法二

    承接上一篇, Object keys object的keys方法能够获取一个给定对象的所有键(key/属性名)并以数组的形式返回.这个方法可以用于键的筛选.匹配等. var basket = { st ...

  4. DDD 领域驱动设计-谈谈 Repository、IUnitOfWork 和 IDbContext 的实践(3)

    上一篇:<DDD 领域驱动设计-谈谈 Repository.IUnitOfWork 和 IDbContext 的实践(2)> 这篇文章主要是对 DDD.Sample 框架增加 Transa ...

  5. STM32F429 LCD程序移植

    STM32F429自带LCD驱动器,这一具有功能给我等纠结于屏幕驱动的程序员带来了很大的福音.有经验的读者一定有过这样的经历,用FSMC驱动带由控制器的屏幕时候,一旦驱动芯片更换,则需要重新针对此驱动 ...

  6. c#比较两个数组的差异

    将DataTable中某一列数据直接转换成数组进行比较,使用的Linq,要引用命名空间using System.Linq; string[] arrRate = dtRate.AsEnumerable ...

  7. BPM合同管理解决方案分享

    一.方案概述合同是组织与组织间所订协议的法律 表现形式,体现着双方对于合作在法律和道德上的承诺.然而,大多数企业的合同管理都或多或少存在合同审批过程不规范.签订草率.审批权责不清.合同执行跟踪难.合同 ...

  8. 听H3絮叨:何以让天下没有难用的流程

    最近朋友圈.网站新闻铺天盖地是"让天下没有难用的流程",有人就要问了,H3 BPM何德何能,为BPM站台,让天下没有难用的流程? 这是一个关于"办公室空想"的故 ...

  9. 【转】Android开发中让你省时省力的方法、类、接口

    转载 http://www.toutiao.com/i6362292864885457410/?tt_from=mobile_qq&utm_campaign=client_share& ...

  10. SQL Server中SELECT会真的阻塞SELECT吗?

    在SQL Server中,我们知道一个SELECT语句执行过程中只会申请一些意向共享锁(IS) 与共享锁(S), 例如我使用SQL Profile跟踪会话86执行SELECT * FROM dbo.T ...