WPF程序设计 :第三章 内容的概念(The Concept of Content)

Window类的Content是从ContentControl类继承来的。ContentControl继承自Control,而Window直接继承自ContentControl。ContentControl类存在的意义几乎就是为了定义Content property以及几个相关的property和方法。

Content property被定义成Object类型,这似乎暗示着它可以是任何对象,事实也相去不远。"相去不远"是因为你不可以把Content property设定成另一个Window类型的对象。运行时会报错,异常信息提醒开发者:Window必须是"树根",而不可以是另一个Window对象的分支。

实例程序-1:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;

namespace PartThree
{
    
public class DisplaysomeText : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new DisplaysomeText());
        }

        
public DisplaysomeText()
        {
            Title 
= "Display some Text";
            Content 
= new int[57];//EventArgs.Empty;//DateTime.Now;//Math.PI;

            Brush brush 
= new LinearGradientBrush(Colors.Black, Colors.White, new Point(00), new Point(11));
            Background 
= brush;
            Foreground 
= brush;
            FontFamily 
= new FontFamily("Comic Sans MS");
            FontSize 
= 50;
            SizeToContent 
= SizeToContent.WidthAndHeight;
            BorderBrush 
= Brushes.SaddleBrown;
            BorderThickness 
= new Thickness(255075100);
        }
    }
}

SizeToContent = SizeToContent.WidthAndHeight;

Window类的SizeToContent property造成窗口大小可以根据内容的尺寸作调整。

实例程序 - 2:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;

namespace PartThree
{
    
public class RecordKeyStrokes : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new RecordKeyStrokes());
        }

        StringBuilder build 
= new StringBuilder("text");

        
public RecordKeyStrokes()
        {
            Title 
= "Record Key Strokes";
            Content 
= build;
        }

        
protected override void OnTextInput(System.Windows.Input.TextCompositionEventArgs e)
        {
            
base.OnTextInput(e);
            
//string str = Content as string;
            if (e.Text == "\b")
            {
                
if (build.Length > 0)
                {
                    build 
= build.Remove(build.Length - 11);
                }
            }
            
else
            {
                build.Append(e.Text);
            }
            Content 
= null;
            Content 
= build;
        }
    }
}

Content Property 真正需要的是本质上更图形化的东西,由UIElement继承而来的类的实例。

在WPF中,UIElement是一个极为重要的类。它实现键盘、鼠标以及手写笔(stylus)事件的处理。UIElement类也包含一个很重要的方法,名为OnRender。这个方法被调用,以显示对象的外观。(待会有实例程序)

(你可以看到在DrawingContent对象中包含很多Draw..的方法)

在Content property的世界中,分成两组对象:一组继承自UIElement,另一组则不是。后面一组的显示结果,就是ToString方法的返回值;前面这一组,则是利用OnRender来显示。继承自UIElement的类(以及对象)被称为element。对这句话的理解参见实例程序-1和实例程序-2。

唯一直接继承UIElement的类是FrameworkElement,在WPF中所有的element都是继承自FrameworkElement。理论上,UIELement提供关于关于用户界面和屏幕显示的必要结构。可以支持各种各样的编程框架(programming framework)。WPF正是这样的框架,它包含继承自FrameworkElement的所有类。

一个重要的类 Image,它是继承自FrameworkElement的一个很常见的类型。下面是它的继承层次:

 

Object

      DispatcherObject(abstract)

             DependencyObject

                       Visual(abstract)

                                  UIElement

                                          FrameworkElement

                                                  Image

 

Image(图像)类让你可以轻易地在文件(document)或者应用内包含影像。

实例程序 - 3,image

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Media;

namespace PartThree
{
    
public class ShowMyFace : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new ShowMyFace());
        }

        
public ShowMyFace()
        {
            Title 
= "Show my face";

            Uri uri 
= new Uri(@"D://MyFace.jpg");
            BitmapImage bitmap 
= new BitmapImage(uri);
            Image img 
= new Image();
            img.Stretch 
= Stretch.None;
            img.HorizontalAlignment 
= HorizontalAlignment.Center;
            img.VerticalAlignment 
= VerticalAlignment.Center;
            img.Source 
= bitmap;
            img.Margin 
= new Thickness(19296480);
            
//img.Width = 300;
            
//img.Height = 500;
            Content = img;
            
//SizeToContent = SizeToContent.WidthAndHeight;

            img.Opacity 
= 0.5;

            Background
=new LinearGradientBrush(Colors.Red,Colors.Blue,new Point(0,0),new Point(1,1));
            img.LayoutTransform 
= new RotateTransform(45);
        }
    }
}
Uri uri = new Uri(System.IO.Path.Combine(Enviroment.GetEnviromentVariable("windir"),"Gone Fishing.bmp"));

Enviroment.GetEnviromentVariable("windir")方法取出"windir"环境变量,其值看起来像是"C:\\WINDOWS"这样的字符串。Path Combine方法结合图片文件的路径名称和文件名,这样就不用费心正确地插入斜杠了。

Image类不具有自己的Background和Foreground property,因为这两个property是由Control类所定义的,而Image并非继承自Control。在早期Winows API中,几乎屏幕上所有的东西,都被认为是控件(Control),现在却不是如此。

控件是视觉对象(visual object),其特征是对用户的输入有反应,像Image这样的element,当然可以得到用户的输入,因为所有的键盘、鼠标、手写笔的输入事件都是由UIElement所定义的。

下面是System.Windows.Shapes这个命名空间,它包含名为Shape的抽象类,以及6个子类。

Object

      DispatcherObject(abstract)

             DependencyObject

                       Visual(abstract)

                                  UIElement

                                          FrameworkElement

                                                  Shape(abstract)

                                                          Ellipse

                                                          Line

                                                          Path

                                                          Polygon(多边形)

                                                          Polyline(多叉线)

                                                          Rectangle               

     

虽然Image是现实点阵图像(raster图像)的标准做法,这些Shape类实现了简单的二维(two-dimensional)矢量图(Vector graphic) 。线面的程序创建一个椭圆(Ellipse)类的对象。

实例程序 - 4, Ellipse

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;

namespace PartThree
{
    
public class ShapeAnEllipse : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new ShapeAnEllipse());
        }

        Ellipse elips;

        
public ShapeAnEllipse()
        {
            Title 
= "Shape an Ellipse";
            elips 
= new Ellipse();
            elips.Fill 
= Brushes.AliceBlue;
            elips.StrokeThickness 
= 24;
            elips.Width 
= 300;
            elips.Height 
= 300;
            elips.Stroke 
= new LinearGradientBrush(Colors.CadetBlue, Colors.Chocolate, new Point(10), new Point(01));
            
//elips.HorizontalAlignment = HorizontalAlignment.Stretch;
            
//elips.VerticalAlignment = VerticalAlignment.Stretch;
            Content = elips;
        }

        
protected override void OnMouseDown(System.Windows.Input.MouseButtonEventArgs e)
        {
            
base.OnMouseDown(e);
            
double a = elips.ActualHeight;
        }
    }
}

该椭圆会填满客户区。周长是1/4英寸粗且使用了渐变画刷。(椭圆内部使用的是AliceBlue画刷来着色,这个颜色是以美国总统罗斯福的女儿来命名的)。

注意的是:不管是Shape类还是Ellispe类,都没有定义任何property可以让我们用来设定椭圆的尺寸,但是Ellispe类从FrameworkElement继承到的Width和Height property,这两个property正式作此用的:

  elips.Wdith = 300;

  elips.Height = 300; 

上面实例程序,如何将Window的Conteng property 设定为字符串,以及如何设定此文字的font。然而,你直接设定到Content property的文字,总具有相同的格式,比方说,你无法将其中几个字设定格式如粗体或斜体。

如果你需要这样,就不要将Content property设定成字符串,而不是设定成一个TextBlock类型的对象,请看下面实例程序:

实例程序 - 5:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace PartThree
{
    
public class FormatTheText : Window
    {
        
public FormatTheText()
        {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new FormatTheText());
        }
            Title 
= "Format The Text";

            TextBlock txt 
= new TextBlock();
            txt.FontSize 
= 32;
            txt.Inlines.Add(
"This is some");
            txt.Inlines.Add(
new Italic(new Run("italic")));
            txt.Inlines.Add(
"text ,and this is some ");
            txt.Inlines.Add(
new Bold(new Run("bold")));
            txt.Inlines.Add(
"text,and let's cap it off  with some ");
            txt.Inlines.Add(
new Bold(new Italic(new Run("bold italic"))));
            txt.Inlines.Add(
"text.");
            txt.TextWrapping 
= TextWrapping.Wrap;

            Content 
= txt;
        }
    }
}

其实,当你将Content property设定成字符串,那么ContentControl(这是Window的祖先类)会先创建一个Text Block类型的对象,以实际显示出此字符串,Text Block类直接继承自FrameworkElement,它定义了Inlines property(类型为InlineCollection,这是Inline对象的collection)。

TextBlock本省属于System.Windows.Controls命名空间。但是 Inline是属于System.Windows.Document命名空间,而且甚至不是继承自UIElement。下面是部分的类继承图,显示出Inline和它的 后代:

Object

      DispatcherObject(abstract)

             DependencyObject

                       ContentElement

                                  FrameworkContentElement

                                          TextElement(abstract)

                                                  Inline(abstract)

                                                          Run

                                                          Span

                                                               Bold

                                                               Italic

                                                               UnderLine               

     

你可能注意到这个类层次与之前的似乎有类似的结构 ---- ContentElement 和 FrameworkContentElement 类对比于 UIElement和FrameworkElement类。然而,ContentElement类不包含OnRender方法。继承自ContentElement的类所产生的对象,不会在屏幕上把自己画出来。它们却是要通过"继承自UIElement的类"才能在屏幕上达到视觉的演示,由"继承自UIElement的类"提供他们所欠缺的OnRender方法。

还有一点,不要把ContentElement 类和ContentControl混为一谈。ContentControl是控件,像Window一样,可以具有Content property。就算 Content property是空的,ContentControl对象还是会在屏幕上显示自己。而ContentElment对象一定要是其他可以显示的对象的一部分(也就是说,必须是控件的内容),才能得以显示。

类似UIElement类,ContentElement类定义了许多用户输入事件,可以把事件处理器(event handler)安装到"由TextBlock显示出来的" Inline elements上.下面的程序展示此技巧。

实例程序 - 6:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;

namespace PartThree
{
    
public class ToggleBoldAndOtalic : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new ToggleBoldAndOtalic());
        }

        
public ToggleBoldAndOtalic()
        {
            Title 
= "Toggle Bold & Italic";

            TextBlock txt 
= new TextBlock();
            txt.FontSize 
= 32;
            txt.HorizontalAlignment 
= HorizontalAlignment.Center;
            txt.VerticalAlignment 
= VerticalAlignment.Center;

            Content 
= txt;

            
string strQuote = "To be, or Not to be, That is the question";
            
string[] strWords = strQuote.Split();

            
foreach (string str in strWords)
            {
                Run run 
= new Run();
                run.Text 
= str;
                run.MouseDown 
+= new System.Windows.Input.MouseButtonEventHandler(run_MouseDown);
                txt.Inlines.Add(run);
                txt.Inlines.Add(
" ");
            }
        }

        
void run_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            Run run 
= sender as Run;

            
if (e.ChangedButton == MouseButton.Left)
                run.FontStyle 
= run.FontStyle == FontStyles.Italic ? FontStyles.Normal : FontStyles.Italic;

            
if (e.ChangedButton == MouseButton.Right)
                run.FontWeight 
= run.FontWeight == FontWeights.Bold ? FontWeights.Normal : FontWeights.Bold;
        }
    }
}

前面其实提到,窗口的Content property其实想要的是继承自UIElement类的实例,因为此类定义了一个名为OnRender的方法,负责在屏幕上显示此对象。

下面这个实例程序,让SimpleEllipse继承自FrameworkElement(这是直接继承自UIElement类的唯一类别),他相当重要的OnRender方法予以override,以获得一个DrawingContent对象。通过此对象和DrwingEllipse方法就可以绘制椭圆了。

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Windows.Shapes;

namespace PartThree
{
    
public class SimpleEllispe : FrameworkElement
    {
        
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
        {
            drawingContext.DrawEllipse(Brushes.Blue, 
new Pen(Brushes.Blue, 24), new Point(RenderSize.Width / 2, RenderSize.Height / 2), RenderSize.Width / 2, RenderSize.Height / 2);
            drawingContext.
        }
    }

    
public class RenderTheGraphic : Window
    {
        [STAThread]
        
public static void Main()
        {
            Application app 
= new Application();
            app.Run(
new RenderTheGraphic());
        }

        
public RenderTheGraphic()
        {
            Title 
= "Render the Graphic";

            SimpleEllispe elips 
= new SimpleEllispe();
            elips.Width 
= 120;
            elips.Height 
= 100;
            Content 
= elips;
        }
    }
}

总结:虽然本章使用到了System.Windows.Controls命名空间的element,但是没有使用任何继承自Control的类(当然Window本省是个例外)。控件是设计来获得用户的输入,并作出反应的。下一章,我们来展开这一范畴。

原文地址:https://www.cnblogs.com/Dlonghow/p/1283718.html