Silverlight开发中的疑难杂症-控件设计篇-如何实现一个EditorBox

EditorBox就是一个具有编辑和展示两种状态的TextBox,因为在最近的工作和学习项目中,多次碰到了需要将一个TextBox以编辑和展示两种不同的样式存在,于是就想到了制作一个这样的控件来提高生产效率,同时也尝试一下自定义控件的开发。该控件包括如下功能:

能在编辑和展示状态之间切换;

可以设置是否能够编辑;

在展示状态双击控件,进入到编辑状态(如果支持编辑);

在编辑状态,输入完文本,回车后进入展示状态;

提供一个Text属性供外部使用;

能够设置展示状态下文本样式(设置指定区间的文本的字体、字体大小、字体颜色等);

基本的实现思路是这样的:首先,定义两个TemplatePart,分别为TextBoxTextBlock类型,用来表示编辑框跟展示框,文本格式的处理通过动态计算所设置的格式,然后添加多个Run元素来实现;然后,定一个两个TemplateVisualState,用来实现编辑状态和展示状态之间的切换。附加的Attribute声明如下:

    [TemplatePart(Name = "PART_Editor", Type = typeof(TextBox))]

    [TemplatePart(Name = "PART_View", Type = typeof(TextBlock))]

    [TemplateVisualState(Name = "Edit", GroupName = "CommonStates")]

    [TemplateVisualState(Name = "View", GroupName = "CommonStates")]

为了使控件使用者能够对样式进行更好的控制,这里定义了一个TextFormat类,与单个的样式设置对应,里面包括字体、字体大小、字体颜色、样式应用的起始索引、应用的总长度,具体的类实现如下:

ExpandedBlockStart.gif TextFormat
///   <summary>
///  文本格式
///   </summary>
public   class  TextFormat : DependencyObject
{
    
///   <summary>
    
///  字体
    
///   </summary>
     public  FontFamily FontFamily
    {
        
get  {  return  (FontFamily)GetValue(FontFamilyProperty); }
        
set  { SetValue(FontFamilyProperty, value); }
    }

    
public   static   readonly  DependencyProperty FontFamilyProperty  =
        DependencyProperty.Register(
" FontFamily " typeof (FontFamily),  typeof (TextFormat),  new  PropertyMetadata( default (FontFamily)));

    
///   <summary>
    
///  字体大小
    
///   </summary>
     public   double  FontSize
    {
        
get  {  return  ( double )GetValue(FontSizeProperty); }
        
set  { SetValue(FontSizeProperty, value); }
    }

    
public   static   readonly  DependencyProperty FontSizeProperty  =
        DependencyProperty.Register(
" FontSize " typeof ( double ),  typeof (TextFormat),  new  PropertyMetadata( 10.0 ));

    
///   <summary>
    
///  字体颜色
    
///   </summary>
     public  Brush Foreground
    {
        
get  {  return  (Brush)GetValue(ForegroundProperty); }
        
set  { SetValue(ForegroundProperty, value); }
    }

    
public   static   readonly  DependencyProperty ForegroundProperty  =
        DependencyProperty.Register(
" Foreground " typeof (Brush),  typeof (TextFormat),  new  PropertyMetadata( new  SolidColorBrush(Colors.Black)));

    
///   <summary>
    
///  样式应用的起始索引
    
///   </summary>
     public   int  StartIndex
    {
        
get  {  return  ( int )GetValue(StartIndexProperty); }
        
set  { SetValue(StartIndexProperty, value); }
    }

    
public   static   readonly  DependencyProperty StartIndexProperty  =
        DependencyProperty.Register(
" StartIndex " typeof ( int ),  typeof (TextFormat),  new  PropertyMetadata( 0 ));

    
///   <summary>
    
///  样式应用的长度
    
///   </summary>
     public   int  Length
    {
        
get  {  return  ( int )GetValue(LengthProperty); }
        
set  { SetValue(LengthProperty, value); }
    }

    
public   static   readonly  DependencyProperty LengthProperty  =
        DependencyProperty.Register(
" Length " typeof ( int ),  typeof (TextFormat),  new  PropertyMetadata( 0 ));
}

///   <summary>
///  文本格式集合
///   </summary>
public   class  TextFormatCollection : ObservableCollection < TextFormat >
{       
}

之后是依赖属性的定义,除了之前提到过的文本格式集合以及当前选择的模式之外,还包括对外提供的文本和是否允许编辑选项,同时在文本格式集合以及当前选择的模式改变时进行文本格式化处理,依赖属性的定义如下:

ExpandedBlockStart.gif Dependency Properties
#region  Dependency Properties

///   <summary>
///  文本格式集合
///   </summary>
public  TextFormatCollection TextFormats
{
    
get  {  return  (TextFormatCollection)GetValue(TextFormatsProperty); }
    
set  { SetValue(TextFormatsProperty, value); }
}

public   static   readonly  DependencyProperty TextFormatsProperty  =
    DependencyProperty.Register(
" TextFormats " typeof (TextFormatCollection),  typeof (EditorBox),  new  PropertyMetadata( new  PropertyChangedCallback(OnTextFormatsChanged)));

///   <summary>
///  文本
///   </summary>
public   string  Text
{
    
get  {  return  ( string )GetValue(TextProperty); }
    
set  { SetValue(TextProperty, value); }
}

public   static   readonly  DependencyProperty TextProperty  =
    DependencyProperty.Register(
" Text " typeof ( string ),  typeof (EditorBox),  new  PropertyMetadata( string .Empty));

///   <summary>
///  是否允许编辑
///   </summary>
public   bool  CanEdit
{
    
get  {  return  ( bool )GetValue(CanEditProperty); }
    
set  { SetValue(CanEditProperty, value); }
}

public   static   readonly  DependencyProperty CanEditProperty  =
    DependencyProperty.Register(
" CanEdit " typeof ( bool ),  typeof (EditorBox),  new  PropertyMetadata( true ));

///   <summary>
///  当前模式
///   </summary>
public  Mode CurrentMode
{
    
get  {  return  (Mode)GetValue(CurrentModeProperty); }
    
set  { SetValue(CurrentModeProperty, value); }
}

public   static   readonly  DependencyProperty CurrentModeProperty  =
    DependencyProperty.Register(
" CurrentMode " typeof (Mode),  typeof (EditorBox),  new  PropertyMetadata(Mode.View,  new  PropertyChangedCallback(OnCurrentModeChanged)));

#region  Property Change Handler

private   static   void  OnTextFormatsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    (obj 
as  EditorBox).OnTextFormatsChanged(e.OldValue  as  TextFormatCollection, e.NewValue  as  TextFormatCollection);
}

///   <summary>
///  文本格式设置改变时,重新计算文本格式
///   </summary>
///   <param name="oldCollection"></param>
///   <param name="newCollection"></param>
protected   virtual   void  OnTextFormatsChanged(TextFormatCollection oldCollection, TextFormatCollection newCollection)
{
    
if  (oldCollection  !=   null )
    {
        oldCollection.CollectionChanged 
-=   new  NotifyCollectionChangedEventHandler(TextFormats_CollectionChanged);
    }
    
if  (newCollection  !=   null )
    {
        newCollection.CollectionChanged 
+=   new  NotifyCollectionChangedEventHandler(TextFormats_CollectionChanged);
    }
    
// 集合改变时重新计算文本格式
    ProcessTextFormat();
}

///   <summary>
///  集合项改变时,重新计算文本格式
///   </summary>
///   <param name="sender"></param>
///   <param name="e"></param>
void  TextFormats_CollectionChanged( object  sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    ProcessTextFormat();
}

private   static   void  OnCurrentModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
    (obj 
as  EditorBox).OnCurrentModeChanged((Mode)e.OldValue, (Mode)e.NewValue);
}

///   <summary>
///  从编辑模式切换到视图模式,进行文本格式计算
///   </summary>
///   <param name="oldMode"></param>
///   <param name="newMode"></param>
protected   virtual   void  OnCurrentModeChanged(Mode oldMode, Mode newMode)
{
    
if  (newMode  ==  Mode.View)
    {
        ProcessTextFormat();
    }
}

#endregion

#endregion

由于使用了TemplatePart定义实现了界面与控件行为逻辑之间的解耦,那么自然的需要在运行时拿到在样式中所定义的TemplatePart。这里通过重载OnApplyTemplate方法来实现子控件的查找,以及相应的处理事件的附加,实现代码如下:

ExpandedBlockStart.gif OnApplyTemplate
public   override   void  OnApplyTemplate()
{
    
base .OnApplyTemplate();
    AttachToVisualTree();
}

///   <summary>
///  获取模板中的子控件,并附加处理
///   </summary>
void  AttachToVisualTree()
{
    
// 获取模板中的子控件
    _editor  =  GetChildControl < TextBox > (PART_Editor);
    _viewer 
=  GetChildControl < TextBlock > (PART_View);

    
if  (_editor  !=   null )
    {
        
// 由于Silverlight的TextChanged事件只在Load之后才会触发,所以需要在Load之后初始化文本格式
        _editor.Loaded  +=   new  RoutedEventHandler(InitTextFormat);
        
// 按下回车回到视图模式
        _editor.KeyDown  +=   new  KeyEventHandler(EnterViewMode);
        
// 设置绑定关系
        _editor.Text  =   this .Text;
        
this .SetBinding(TextProperty,  new  Binding( " Text " ) { Source  =  _editor, Mode  =  BindingMode.TwoWay });                
    }
    ProcessTextFormat();
}

在实际测试时,这里发现了一个问题,当我在上面的方法中设置TextBoxText属性后,对应控件中注册的TextChanged事件并没有触发,经过多次的调试,发现似乎只有在控件Load完之后进行的Text属性赋值操作,才会引起TextChanged事件;然而测试了WPF中的TextBox,并没有发现有一样的问题,在网上也没有发现有类似的讨论,只好作罢。最后通过注册TextBoxLoaded事件,并在里面重新进行了文本格式的处理。如果有对这个问题有所了解的朋友,希望能够给我答疑解惑~

接下来是最重要的文本格式的处理,这部分的具体思路是这样的:

1.         判断是否处于展示模式;

2.         清楚原有的Inlines集合;

3.         TextFormats集合中的元素按照StartIndex从小到大进行排序;

4.         循环处理TextFormats集合中的元素;

5.         如果当前格式覆盖了前面的格式(StartIndex>LastIndex),则抛出异常;

6.         如果当前格式与前面的格式之间有空隙,则将空隙单独设置为默认格式;

7.         按照当前格式进行设置;

8.         循环结束,如果还有剩余的文本,则全部用默认格式处理。

最后附上完整的代码以及默认的控件样式:

ExpandedBlockStart.gif EditorBox
[TemplatePart(Name  =   " PART_Editor " , Type  =   typeof (TextBox))]
[TemplatePart(Name 
=   " PART_View " , Type  =   typeof (TextBlock))]
[TemplateVisualState(Name 
=   " Edit " , GroupName  =   " CommonStates " )]
[TemplateVisualState(Name 
=   " View " , GroupName  =   " CommonStates " )]
public   class  EditorBox : Control
{
    
public   const   string  PART_Editor  =   " PART_Editor " ;
    
public   const   string  PART_View  =   " PART_View " ;
    
public   const   string  VisualState_Edit  =   " Edit " ;
    
public   const   string  VisualState_View  =   " View " ;

    
///   <summary>
    
///  模式
    
///   </summary>
     public   enum  Mode
    {
        
///   <summary>
        
///  查看模式
        
///   </summary>
        View,
        
///   <summary>
        
///  编辑模式
        
///   </summary>
        Edit
    }

    
#region  Private Fields

    
private  TextBox _editor;
    
private  TextBlock _viewer;

    
#endregion

    
#region  Dependency Properties

    
///   <summary>
    
///  文本格式集合
    
///   </summary>
     public  TextFormatCollection TextFormats
    {
        
get  {  return  (TextFormatCollection)GetValue(TextFormatsProperty); }
        
set  { SetValue(TextFormatsProperty, value); }
    }

    
public   static   readonly  DependencyProperty TextFormatsProperty  =
        DependencyProperty.Register(
" TextFormats " typeof (TextFormatCollection),  typeof (EditorBox),  new  PropertyMetadata( new  PropertyChangedCallback(OnTextFormatsChanged)));

    
///   <summary>
    
///  文本
    
///   </summary>
     public   string  Text
    {
        
get  {  return  ( string )GetValue(TextProperty); }
        
set  { SetValue(TextProperty, value); }
    }

    
public   static   readonly  DependencyProperty TextProperty  =
        DependencyProperty.Register(
" Text " typeof ( string ),  typeof (EditorBox),  new  PropertyMetadata( string .Empty));

    
///   <summary>
    
///  是否允许编辑
    
///   </summary>
     public   bool  CanEdit
    {
        
get  {  return  ( bool )GetValue(CanEditProperty); }
        
set  { SetValue(CanEditProperty, value); }
    }

    
public   static   readonly  DependencyProperty CanEditProperty  =
        DependencyProperty.Register(
" CanEdit " typeof ( bool ),  typeof (EditorBox),  new  PropertyMetadata( true ));

    
///   <summary>
    
///  当前模式
    
///   </summary>
     public  Mode CurrentMode
    {
        
get  {  return  (Mode)GetValue(CurrentModeProperty); }
        
set  { SetValue(CurrentModeProperty, value); }
    }

    
public   static   readonly  DependencyProperty CurrentModeProperty  =
        DependencyProperty.Register(
" CurrentMode " typeof (Mode),  typeof (EditorBox),  new  PropertyMetadata(Mode.View,  new  PropertyChangedCallback(OnCurrentModeChanged)));

    
#region  Property Change Handler

    
private   static   void  OnTextFormatsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        (obj 
as  EditorBox).OnTextFormatsChanged(e.OldValue  as  TextFormatCollection, e.NewValue  as  TextFormatCollection);
    }

    
///   <summary>
    
///  文本格式设置改变时,重新计算文本格式
    
///   </summary>
    
///   <param name="oldCollection"></param>
    
///   <param name="newCollection"></param>
     protected   virtual   void  OnTextFormatsChanged(TextFormatCollection oldCollection, TextFormatCollection newCollection)
    {
        
if  (oldCollection  !=   null )
        {
            oldCollection.CollectionChanged 
-=   new  NotifyCollectionChangedEventHandler(TextFormats_CollectionChanged);
        }
        
if  (newCollection  !=   null )
        {
            newCollection.CollectionChanged 
+=   new  NotifyCollectionChangedEventHandler(TextFormats_CollectionChanged);
        }
        
// 集合改变时重新计算文本格式
        ProcessTextFormat();
    }

    
///   <summary>
    
///  集合项改变时,重新计算文本格式
    
///   </summary>
    
///   <param name="sender"></param>
    
///   <param name="e"></param>
     void  TextFormats_CollectionChanged( object  sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ProcessTextFormat();
    }

    
private   static   void  OnCurrentModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        (obj 
as  EditorBox).OnCurrentModeChanged((Mode)e.OldValue, (Mode)e.NewValue);
    }

    
///   <summary>
    
///  从编辑模式切换到视图模式,进行文本格式计算
    
///   </summary>
    
///   <param name="oldMode"></param>
    
///   <param name="newMode"></param>
     protected   virtual   void  OnCurrentModeChanged(Mode oldMode, Mode newMode)
    {
        
if  (newMode  ==  Mode.View)
        {
            ProcessTextFormat();
        }
    }

    
#endregion

    
#endregion

    
public  EditorBox()
    {
        
this .DefaultStyleKey  =   typeof (EditorBox);
        TextFormats 
=   new  TextFormatCollection();
        
// 通过附加属性增加鼠标双击事件
         this .SetValue(MouseEventHelper.MouseDoubleClickProperty,  new  MouseButtonEventHandler(MouseDoubleClick));
    }

    
public   override   void  OnApplyTemplate()
    {
        
base .OnApplyTemplate();
        AttachToVisualTree();
    }

    
///   <summary>
    
///  获取模板中的子控件,并附加处理
    
///   </summary>
     void  AttachToVisualTree()
    {
        
// 获取模板中的子控件
        _editor  =  GetChildControl < TextBox > (PART_Editor);
        _viewer 
=  GetChildControl < TextBlock > (PART_View);

        
if  (_editor  !=   null )
        {
            
// 由于Silverlight的TextChanged事件只在Load之后才会触发,所以需要在Load之后初始化文本格式
            _editor.Loaded  +=   new  RoutedEventHandler(InitTextFormat);
            
// 按下回车回到视图模式
            _editor.KeyDown  +=   new  KeyEventHandler(EnterViewMode);
            
// 设置绑定关系
            _editor.Text  =   this .Text;
            
this .SetBinding(TextProperty,  new  Binding( " Text " ) { Source  =  _editor, Mode  =  BindingMode.TwoWay });                
        }
        ProcessTextFormat();
    }

    
///   <summary>
    
///  第一次加载时,初始化文本格式
    
///   </summary>
    
///   <param name="sender"></param>
    
///   <param name="e"></param>
     void  InitTextFormat( object  sender, RoutedEventArgs e)
    {
        ProcessTextFormat();
    }

    
///   <summary>
    
///  进入视图模式
    
///   </summary>
    
///   <param name="sender"></param>
    
///   <param name="e"></param>
     void  EnterViewMode( object  sender, KeyEventArgs e)
    {
        
// 按回车进入查看状态
         if  (e.Key  ==  Key.Enter)
        {
            VisualStateManager.GoToState(
this , VisualState_View,  false );
            CurrentMode 
=  Mode.View;
        }
    }

    
///   <summary>
    
///  双击进入编辑模式(如果允许编辑)
    
///   </summary>
    
///   <param name="sender"></param>
    
///   <param name="e"></param>
     void  MouseDoubleClick( object  sender, MouseButtonEventArgs e)
    {
        
// 更换VisualStatus  双击进入编辑状态         
         if  (CanEdit)
        {
            VisualStateManager.GoToState(
this , VisualState_Edit,  false );
            CurrentMode 
=  Mode.Edit;
        }
    }

    
///   <summary>
    
///  处理文本格式
    
///   </summary>
     void  ProcessTextFormat()
    {
        
if  (_viewer  !=   null   &&  CurrentMode  ==  Mode.View  &&   this .TextFormats  !=   null )
        {
            _viewer.Inlines.Clear();

            
// 先按照StartIndex排序
            var formats  =   this .TextFormats.OrderBy((format)  =>  {  return  format.StartIndex; }).ToList();

            
int  startIndex  =   0 ;
            
int  length  =   0 ;
            
for  ( int  i  =   0 ; i  <  formats.Count; i ++ )
            {
                
if  (startIndex  >=   this .Text.Length)
                    
break ;

                TextFormat format 
=  formats[i];
                Run run 
=   new  Run();
                
// 重叠部分
                 if  (format.StartIndex  <  startIndex)
                {
                    
throw   new  Exception( " StartIndex不能重叠 " );
                }
                
// 不要求格式部分
                 else   if  (format.StartIndex  >  startIndex)
                {
                    length 
=  Math.Min(format.StartIndex  -  startIndex,  this .Text.Length  -  startIndex);
                    run.Text 
=   this .Text.Substring(startIndex, length);
                    startIndex 
+=  length;
                    i
-- ;
                }
                
// 要求格式部分
                 else   if  (format.StartIndex  ==  startIndex)
                {

                    length 
=  Math.Min(format.Length,  this .Text.Length  -  startIndex);
                    run.Text 
=   this .Text.Substring(startIndex, length);

                    
if  (format.FontFamily  !=   null )
                        run.FontFamily 
=  format.FontFamily;
                    run.FontSize 
=  format.FontSize;
                    run.Foreground 
=  format.Foreground;
                    startIndex 
+=  length;
                }
                _viewer.Inlines.Add(run);
            }
            
// 处理尾部的剩余部分
             if  (startIndex  <   this .Text.Length)
            {
                Run run 
=   new  Run();
                length 
=   this .Text.Length  -  startIndex;
                run.Text 
=   this .Text.Substring(startIndex, length);
                _viewer.Inlines.Add(run);
            }
        }
    }

    
///   <summary>
    
///  获取指定名字的控件,并转换为对应类型
    
///   </summary>
    
///   <typeparam name="T"> 控件类型 </typeparam>
    
///   <param name="ctrlName"> 控件名 </param>
    
///   <returns> 转换后的控件 </returns>
     protected  T GetChildControl < T > ( string  ctrlName)  where  T :  class
    {
        T ctrl 
=  GetTemplateChild(ctrlName)  as  T;
        
return  ctrl;
    }
}
ExpandedBlockStart.gif 默认样式
< ResourceDictionary
    
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local
="clr-namespace:YQL.CustomControlLibs"  xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"  xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"  mc:Ignorable ="d"   >
    
< Style  TargetType ="local:EditorBox" >
        
< Setter  Property ="Template" >
            
< Setter.Value >
                
< ControlTemplate  TargetType ="local:EditorBox" >  
                    
< Border  Background =" {TemplateBinding Background} "
                        BorderBrush
=" {TemplateBinding BorderBrush} "
                        BorderThickness
=" {TemplateBinding BorderThickness} " >
                        
< VisualStateManager.VisualStateGroups >
                            
< VisualStateGroup  x:Name ="CommonStates" >
                                
< VisualState  x:Name ="Edit" >
                                    
< Storyboard >
                                        
< ObjectAnimationUsingKeyFrames  BeginTime ="00:00:00"  Duration ="00:00:00.0010000"  Storyboard.TargetName ="PART_View"  Storyboard.TargetProperty ="(UIElement.Visibility)" >
                                            
< DiscreteObjectKeyFrame  KeyTime ="00:00:00" >
                                                
< DiscreteObjectKeyFrame.Value >
                                                    
< Visibility > Collapsed </ Visibility >
                                                
</ DiscreteObjectKeyFrame.Value >
                                            
</ DiscreteObjectKeyFrame >
                                        
</ ObjectAnimationUsingKeyFrames >
                                        
< ObjectAnimationUsingKeyFrames  BeginTime ="00:00:00"  Duration ="00:00:00.0010000"  Storyboard.TargetName ="PART_Editor"  Storyboard.TargetProperty ="(UIElement.Visibility)" >
                                            
< DiscreteObjectKeyFrame  KeyTime ="00:00:00" >
                                                
< DiscreteObjectKeyFrame.Value >
                                                    
< Visibility > Visible </ Visibility >
                                                
</ DiscreteObjectKeyFrame.Value >
                                            
</ DiscreteObjectKeyFrame >
                                        
</ ObjectAnimationUsingKeyFrames >
                                    
</ Storyboard >
                                
</ VisualState >
                                
< VisualState  x:Name ="View" >
                                    
< Storyboard >
                                        
< ObjectAnimationUsingKeyFrames  BeginTime ="00:00:00"  Duration ="00:00:00.0010000"  Storyboard.TargetName ="PART_View"  Storyboard.TargetProperty ="(UIElement.Visibility)" >
                                            
< DiscreteObjectKeyFrame  KeyTime ="00:00:00" >
                                                
< DiscreteObjectKeyFrame.Value >
                                                    
< Visibility > Visible </ Visibility >
                                                
</ DiscreteObjectKeyFrame.Value >
                                            
</ DiscreteObjectKeyFrame >
                                        
</ ObjectAnimationUsingKeyFrames >
                                        
< ObjectAnimationUsingKeyFrames  BeginTime ="00:00:00"  Duration ="00:00:00.0010000"  Storyboard.TargetName ="PART_Editor"  Storyboard.TargetProperty ="(UIElement.Visibility)" >
                                            
< DiscreteObjectKeyFrame  KeyTime ="00:00:00" >
                                                
< DiscreteObjectKeyFrame.Value >
                                                    
< Visibility > Collapsed </ Visibility >
                                                
</ DiscreteObjectKeyFrame.Value >
                                            
</ DiscreteObjectKeyFrame >
                                        
</ ObjectAnimationUsingKeyFrames >
                                    
</ Storyboard >
                                
</ VisualState >
                            
</ VisualStateGroup >
                        
</ VisualStateManager.VisualStateGroups >
                        
< Grid >
                            
< TextBlock  x:Name ="PART_View" />
                            
< local:NumericBox  x:Name ="PART_Editor"  Visibility ="Collapsed"  MaxFractionDigits ="4" />
                        
</ Grid >                           
                    
</ Border >
                
</ ControlTemplate >
            
</ Setter.Value >
        
</ Setter >
    
</ Style >
</ ResourceDictionary >

 效果预览:

http://www.bbniu.com/matrix/ShowApplication.aspx?id=70

再此特别感谢bbniu提供的Silverlight Host作品秀平台:)

欢迎大家访问www.bbniu.com进行关于WPF和Silverlight方面的技术讨论。

转载于:https://www.cnblogs.com/yingql/archive/2010/03/11/1683841.html

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值