继之前那个控件,又做了一个原理差不多的控件。这个控件主要模仿百度贴吧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. 代码的坏味道(17)——夸夸其谈未来性(Speculative Generality)

    坏味道--夸夸其谈未来性(Speculative Generality) 特征 存在未被使用的类.函数.字段或参数. 问题原因 有时,代码仅仅为了支持未来的特性而产生,然而却一直未实现.结果,代码变得 ...

  2. C#与C++通信

    # C#与C++相互发送消息 # ## C#端: ## namespace CshapMessage { /// /// MainWindow.xaml 的交互逻辑 /// public partia ...

  3. mono for android学习过程系列教程(1)

    直接进入主题,关于mono for android的学习,首先配置好环境,如何配置环境,度娘谷歌一大堆,记得使用破解版. 我自己是百度“黑马四期”传智播客的视频,里面有破解版开发环境的软件. 今天直接 ...

  4. 使用LogMaster4Net实现应用程序日志的集中管理

    日志在软件系统中的重要性我在此也不赘述了,几乎所有程序员每天都会更日志打交道. 那么你是否曾今为这样的一些事情而困扰过: - 远程登录到不同的服务器,找到应用程序目然后查看应用日志: - 来回切换于不 ...

  5. React单元测试——十八般兵器齐上阵,环境构建篇

    一个完整.优秀的项目往往离不开单元测试的环节,就 github 上的主流前端项目而言,基本都有相应的单元测试模块. 就 React 的项目来说,一套完整的单元测试能在在后续迭代更新中回归错误时候给与警 ...

  6. Floyd-Warshall 全源最短路径算法

    Floyd-Warshall 算法采用动态规划方案来解决在一个有向图 G = (V, E) 上每对顶点间的最短路径问题,即全源最短路径问题(All-Pairs Shortest Paths Probl ...

  7. Nova PhoneGap框架 总结

    Nova PhoneGap Framework 是完全针对PhoneGap应用程序量身定做的,在这个框架下开发的应用程序很容易实现高质量的代码,很容易让程序拥有很好的性能和用户体验. 在经历了多个项目 ...

  8. Storm介绍(二)

    作者:Jack47 转载请保留作者和原文出处 欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 本文是Storm系列之一,主要介绍Storm的架构设计,推荐读者在阅读 ...

  9. 【初探Spring】------Spring IOC(二):初始化过程---简介

    首先我们先来看看如下一段代码 ClassPathResource resource = new ClassPathResource("bean.xml"); DefaultList ...

  10. 高级SQL运用

    一:什么是数据库设计? 数据库设计就是将数据库中的数据实体以及这些数据实体之间的关系,进行规范和结构化的过程. 二:为什么要实施数据库设计? 1:良好的数据库设计可以有效的解决数据冗余的问题 2:效率 ...