posts - 1180, comments - 162, trackbacks - 1, articles - 5
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

2011年5月26日

在wpf中,在创建完控件之后,系统开始匹配控件所用的Style或者template。在程序中,可以用GetTemplateChild或者FindName来获得Resource中用x:Name="名字"声明的对象。

  FindName是公共方法,而非受保护方法,它使用恰当的名称范围措施,从而可以访问某个元素中的模板并在其中查找命名项。

  FindName()首先评估该FrameworkElement实例是否包含一个命名范围。如果该实例是一个xaml文件的root,则它包含一个命名范围。然而,如果该实例不包含一个命名空间,该方法则遍历可视树(visual tree),查找最近的命名范围,并解析名称。最近的命名范围可能是template的命名范围,style的命名范围和根节点的命名范围。

  FindName方法不会交叉到其他的命名范围,如果一个template被应用到控件当中,在控件中使用FindName方法就不会获得在Template中以x:Name声明的元素了。

  GetTemplateChild该方法被声明为protected internal,所以必须在使用模板控件内部的代码中访问它。

  个人认为,这两个方法最好都用在OnApplyTemplate函数中,在用户自定义控件中用GetTemplateChild,其他情况用FindName。

  如果找到对应的对象之后,一般是对该对象增加某种事件,或者添加某种效果。但是如果要将该对象赋值给另一个FromeWorkElement时,会出现要求你先断开其与父对象的连接,再复制。这时只需用对象.Parent获得父对象,并将其Child或者items清空即可

posted @ 2011-05-26 11:30 linFen 阅读(175) 评论(0) 编辑

  

WPF 4 中DataGrid的模板列做双向数据绑定时,需要在绑定中指定UpdateSourceTrigger,如下:(WPFToolkit February 2010)

<DataGrid VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling" 
Name=
"dataGrid1" AutoGenerateColumns="False" ItemsSource="{Binding}" HorizontalGridLinesBrush=
"{DynamicResource GridLinesBrush}" VerticalGridLinesBrush="{DynamicResource GridLinesBrush}"
 AlternatingRowBackground="{DynamicResource RowBackgroundBrush}" CanUserAddRows="False" 
CanUserResizeRows=
"False" CanUserDeleteRows="False" IsEnabled="True" Margin="0" RowHeight="20" 
 CellStyle=
"{DynamicResource DataGridCellStyle2}" ColumnHeaderStyle="{DynamicResource DataGridColumnHeaderStyle1}" 
RowStyle=
"{DynamicResource DataGridRowStyle1}" RowHeaderStyle="{DynamicResource DataGridRowHeaderStyle1}" 
ItemsPanel=
"{DynamicResource ItemsPanelTemplate1}" RowHeaderWidth="0" IsSynchronizedWithCurrentItem="True"
 SelectionMode="Single" MinWidth="10" BorderThickness="0" GridLinesVisibility="Horizontal" ColumnHeaderHeight="22"
 ScrollViewer.CanContentScroll="False" >
  <DataGrid.Columns>
    <DataGridTemplateColumn Header="选择" CanUserReorder="False">
      <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
          <CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Cursor="Hand"/>
        </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn>
    <DataGridTextColumn Header="标题" Binding="{Binding Title, Mode=OneWay}" />
  </DataGrid.Columns>
</DataGrid>

  IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

  WPFToolkit February 2010则不需要。

posted @ 2011-05-26 11:28 linFen 阅读(470) 评论(0) 编辑

WPF程序中的单位是与设备无关的单位,每个单位是1/96英寸,如果电脑的DPI设置为96(每个英寸96个像素),那么此时每个WPF单位对应一个像素,不过如果电脑的DPI设备为120(每个英寸120个像素),那此时每个WPF单位对应应该是120/96=1.25个像素

  一般在程序中我们常常需要得到当前屏幕的宽和高,常见做法有:

  1.System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width

  System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height

  这两个方法可以返回当前屏幕选择的分辨率,该分辨率是以像素为单位,在DPI为96的情况下我们可以利用它们来做一些控件的定位,因为此时WPF单位对应一个像素,而当DPI非96的情况下,用该分辨率来做定位就会发现误差了,因此此时每个WPF单位并不是对应于一个像素

  2.SystemParameters.PrimaryScreenWidth

  SystemParameters.PrimaryScreenHeight

  这两个方法可以返回当前屏幕的宽和高,它是与设备无关的单位(1/96英寸),因此用它来做控件的定位,在DPI改变的情况下,也不会发生定位上的误差

  3.SystemParameters.WorkArea.Size.Width

  SystemParameters.WorkArea.Size.Height

  这两个方法可以返回当前屏幕工作区的宽和高(除去任务栏),它也是与设备无关的单位,通常我们可以结合2和3来得到任务栏的高度

posted @ 2011-05-26 11:15 linFen 阅读(201) 评论(0) 编辑

初学wpf今天做一个菜鸟级别实例,只适合菜鸟.

  先上图

  

  说明一下功能:显示总条数,选中条数.全选.点击datagrid勾选,也可多选

  datagrid绑定的数据是用的一个数据过渡类,如有一个Student类,类中有Id,Name...

  但没有总条数也没有当前选中条数(当然还有其他情况会跟这种情况类似时也会用到)

  所以我就构造一个数据过渡类StudentData

  //数据实体类代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public class PropertyChangedBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        public void Notify(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    public class StudentData : PropertyChangedBase
    {
        private Student _student;
        public Student student
        {
            get { return _student; }
            set { _student = value; Notify("student"); }
        }
        private int _Count=0;
        /// <summary>
        /// 总条数
        /// </summary>
        public int Count
        {
            get { return _Count; }
            set { _Count = value; Notify("Count"); }
        }
        private int _IsCheckedCount=0;
        /// <summary>
        ///  当前选中条数
        /// </summary>
        public int IsCheckedCount
        {
            get { return _IsCheckedCount; }
            set { _IsCheckedCount = value; Notify("IsCheckedCount"); }
        }
    }

    public class Student : PropertyChangedBase
    {
        private string _Id;
        public string Id
        {
            get { return _Id; }
            set { _Id = value; Notify("Id"); }
        }
        private string _Name;
        public string Name
        {
            get { return _Name; }
            set { _Name = value; Notify("Name"); }
        }
        public bool _IsChecked;
        /// <summary>
        /// 是否选中
        /// </summary>
        public bool IsChecked
        {
            get { return _IsChecked; }
            set { _IsChecked = value; Notify("IsChecked"); }
        }               
    }

  数据实体已经写好,则需要绑定数据了,绑定数据代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/// <summary>
        /// 数据过渡类集合
        /// </summary>
        List<StudentData> LStudentData = new List<StudentData>();
        List<Student> stuList { get; set; }
        public void GetData()
        {
            //获取学生数据集合
            stuList = new List<Student> { 
                new Student{Id="001",Name="zenghai1",IsChecked=false},
                new Student{Id="002",Name="zenghai2",IsChecked=false},
                new Student{Id="003",Name="zenghai3",IsChecked=false},
                new Student{Id="004",Name="zenghai4",IsChecked=false},
                new Student{Id="005",Name="zenghai5",IsChecked=false},
            };
            //循环学生数据,并添加到数据过渡集合中
            foreach (var item in stuList)
            {
                StudentData listStu = new StudentData();
                listStu.student = item;
                listStu.Count = stuList.Count;
                listStu.IsCheckedCount = stuList.Count(p => p.IsChecked == true);
                LStudentData.Add(listStu);
            }
            //绑定
            Grid_Data.DataContext = LStudentData;

        }

  既然数据已经绑定好了,那就贴出xmal的代码,注意datagrid的 CanUserAddRows属性得置为false,不然DataGrid会

  在尾部多出一行,意思为是否允许用户增加行.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<Grid Name="Grid_Data">
        <Grid.RowDefinitions>
            <RowDefinition Height="30" />
            <RowDefinition Height="*" /> 
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <StackPanel Orientation="Horizontal">
            <TextBlock VerticalAlignment="Center">
                <TextBlock Text="当前总条数"></TextBlock>                
                <TextBlock Text="{Binding Path=Count}"></TextBlock>
                <TextBlock Text="条"></TextBlock>
            </TextBlock>                
                <TextBlock  VerticalAlignment="Center" Margin="10,0,0,0" >
                    <TextBlock Text="当前选中条数"></TextBlock>                
                <TextBlock x:Name="Tb_SelectCount" Text="{Binding Path=IsCheckedCount}"></TextBlock>
                <TextBlock Text="条"></TextBlock>
                </TextBlock>
                <Button Name="btn_delete" Click="btn_delete_Click" Content="删除选中"  VerticalAlignment="Center" Margin="10,0,0,0"></Button>
            </StackPanel>
        </Grid>
        <Grid Grid.Row="1">
            <DataGrid Name="DgQuestion" ItemsSource="{Binding }"  Margin="0" Background="White" SelectionChanged="DgQuestion_SelectionChanged" CanUserAddRows="False" AutoGenerateColumns="False"  HorizontalGridLinesBrush="#FFD1CFCF" VerticalGridLinesBrush="#FFD1CFCF">
            <DataGrid.CellStyle>
                <Style TargetType="DataGridCell">
                    <Style.Triggers>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="#DBEDF8"/>
                            <Setter Property="BorderBrush" Value="#DBEDF8"/>
                            <Setter Property="Foreground" Value="Black"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </DataGrid.CellStyle>
            <DataGrid.Columns>
                    <DataGridTemplateColumn>
                    <DataGridTemplateColumn.Header>
                        <CheckBox Content="全 选" x:Name="cBox_All" Click="cBox_All_Click"></CheckBox>
                    </DataGridTemplateColumn.Header>
                       
                        <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                                <CheckBox IsChecked="{Binding Path=student.IsChecked}"></CheckBox>                                
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                    <DataGridTextColumn Header="名称" Width="*" Binding="{Binding Path=student.Name}" IsReadOnly="True"/>                
            </DataGrid.Columns>
        </DataGrid>
        </Grid>       
    </Grid>

  以下是页面中的事件处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//全选按钮事件
        private void cBox_All_Click(object sender, RoutedEventArgs e)
        {
            CheckBox cb = sender as CheckBox;
            LStudentData.FindAll(p =>
            {
                p.student.IsChecked = cb.IsChecked.Value;
                p.Count = stuList.Count;                 
                return true; });
            LStudentData.FindAll(p => { p.IsCheckedCount = stuList.Count(t => t.IsChecked == true); return true; });
        }


        //DataGrid SelectionChanged
        private void DgQuestion_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (DgQuestion.SelectedItems.Count > 1)
            {
                foreach (var item in e.AddedItems)
                {
                    if (item is StudentData)
                    {
                        bool isChecked = (item as StudentData).student.IsChecked;
                        (item as StudentData).student.IsChecked = true;
                    }
                }
            }
            else
            {
                if (e.AddedItems.Count ==1)
                {
                    if (e.AddedItems[0] is StudentData)
                    {
                        bool isChecked = (e.AddedItems[0] as StudentData).student.IsChecked;
                        if (isChecked)
                            (e.AddedItems[0] as StudentData).student.IsChecked = false;
                        else
                            (e.AddedItems[0] as StudentData).student.IsChecked = true;
                    }
                } 
            }
            LStudentData.FindAll(p => { p.IsCheckedCount = stuList.Count(t => t.IsChecked == true); return true; });
        }        
        //删除选中按钮事件
        private void btn_delete_Click(object sender, RoutedEventArgs e)
        {
            LStudentData = LStudentData.FindAll(p => { if (p.student.IsChecked) { p.student.IsChecked = false; return p.student.Id!=p.student.Id;} return true; });
            LStudentData.FindAll(p => { p.Count = LStudentData.Count; return true; });
            LStudentData.FindAll(p => { p.IsCheckedCount = LStudentData.Count(t => t.student.IsChecked == true); return true; });
            if (LStudentData.Count == 0)
                Tb_SelectCount.Text = "0";            
            Grid_Data.DataContext = LStudentData;
        }
    }

posted @ 2011-05-26 10:49 linFen 阅读(404) 评论(0) 编辑

参考资料:

(1). WPF:自动点击某个FrameworkElement

(2). TestApi - a library of Test APIs

(3). Introduction to TestApi – Part 1: Input Injection APIs

1. 模拟用户输入的五种方式:

 (A)直接调用UI element的方法,例如:Button.IsPressed

   (B)利用可用的接口(UIA, MSAA, etc.),例如: AutomationElement

   (C)使用底层输入模拟,与操作系统相关,例如:Windows中的 SendInput Win32 API 和 Raw Input Win32 API

   (D)使用设备驱动模拟

   (E)使用机器人模拟人类操作,例如:敲击键盘

 方法A是framework级别的,只对WPF有效,而对Winform无效;

 方法B比framework级别低一些,但是任然有许多限制,因为一些framework需要的可用接的口实现方式是不同的;

   方法C和D是操作系统级别的,其中D比C要难以实现;

   方法E是一种普遍使用的方法(我想只是在美国吧,汗),虽然它代价昂贵而且速度很慢。

TestApi提供了最常用的B和C两种方式,其中B方式由AutomationUtilities类实现,C方式由Mouse 和 Keyboard两个类实现。

2. 使用TestApi模拟的例子

    例1:在WPF Window中查找并按下一个WPF Button,使用AutomationUtilitiesMouse 类.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//
// EXAMPLE #1
// This code below discovers and clicks the Close button in an About 
//dialog box, thus dismissing the About dialog box.
//

string aboutDialogName = "About";
string closeButtonName = "Close";

AutomationElementCollection aboutDialogs = AutomationUtilities.FindElementsByName(
    AutomationElement.RootElement,
    aboutDialogName);

AutomationElementCollection closeButtons = AutomationUtilities.FindElementsByName(
    aboutDialogs[0],
    closeButtonName);

//
// You can either invoke the discovered control, through its invoke
// pattern...
//

InvokePattern p = 
    closeButtons[0].GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
p.Invoke();

//
// ... or you can handle the Mouse directly and click on the control.
//

Mouse.MoveTo(closeButton.GetClickablePoint());
Mouse.Click(MouseButton.Left);

例2:自动查找一个 TextBox 并在其中打字,使用Mouse 和 Keyboard

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//
// EXAMPLE #2
// Discover the location of a TextBox with a given name.
//

string textboxName = "ssnInputField";

AutomationElement textBox = AutomationUtilities.FindElementsByName(
    AutomationElement.RootElement,
    textboxName)[0];

Point textboxLocation = textbox.GetClickablePoint();

//
// Move the mouse to the textbox, click, then type something
//

Mouse.MoveTo(textboxLocation);
Mouse.Click(MouseButton.Left);

Keyboard.Type("Hello world.");
Keyboard.Press(Key.Shift);
Keyboard.Type("hello, capitalized world.");
Keyboard.Release(Key.Shift);

3. 后记

  TestApi中的Mouse Keyboard类可以在任何窗体应用程序中使用,与测试框架和测试流程无关,并且提供了源代码和文档,你可以集成到自己的project中,也可以直接引用Dll

  需要注意的是,虽然TestApi提供如此简单的方法实现UI测试,但是UI测试是一件棘手而复杂的事情,在任何时候都应该尽量避免。一般来说,宁可在应用程序中采用多层设计模式(multi-tier),而设计一个浅\薄(thin)的UI,以尽量规避UI测试。

4. 附:如何计算控件位置

   如需单独计算控件元素位置,而不是使用TestApi中的GetClickablePoint()方法,可采用以下方法:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/// <summary>
2: /// Get mouse move to location

3: /// </summary>

4: /// <param name="element">element</param>

5: /// <param name="logicalOffset">wpf logical pixel offset</param>

6: /// <returns>screen physical pixel location</returns>

7: public static System.Drawing.Point GetMoveToLocation(FrameworkElement element, Point logicalOffset)

8: {

9:   Point mouseLocation = default(Point);

10:  FlowDirection flowDirection = Window.GetWindow(element).FlowDirection;

11:  

12:  // We don't need to convert element location to physical screen pixel because wpf takes care of it.

13:  Point elementLocation = element.PointToScreen(new Point());

14:  

15: // We need to convert offset to physical screen pixel since we're pass in wpf logical pixel

16: double physicalXOffset = ConvertToPhysicalPixel(logicalOffset.X);

17: double physicalYOffset = ConvertToPhysicalPixel(logicalOffset.Y);

18:  

19: switch (flowDirection)

20:  {

21:    case FlowDirection.LeftToRight:

22:      mouseLocation = new Point(elementLocation.X + physicalXOffset, elementLocation.Y + physicalYOffset);

23:      break;

24:   case FlowDirection.RightToLeft:

25:    // We need to subtract physical offsetX because the element location starting point is in right most

26:     mouseLocation = new Point(elementLocation.X - physicalXOffset, elementLocation.Y + physicalYOffset);

27:     break;

28:  }

29:  

30:   return new System.Drawing.Point(Convert.ToInt32(mouseLocation.X), Convert.ToInt32(mouseLocation.Y));

31: }

32:  

33: /// <summary>

34: /// WPF has its own pixel system in double value type, and screen pixel includes different DPIs is in int value type.

35: /// In 96 dpi, wpf and screen pixels are the same, but other dpi, we need to convert wpf logical pixel to screen physical 

36: /// pixel by using formula (wpf pixel value * dpi / 96.0).

37: /// </summary>

38: /// <param name="logicalPixel">Logical(WPF) pixel value</param>

39: /// <returns>Physical(Screen) pixel value</returns>

40: public static int ConvertToPhysicalPixel(double logicalPixel)

41: {

42:    return Convert.ToInt32(logicalPixel * GetDpi() / 96.0);

43: }

44:  

45: /// <summary>

46: /// Get DPI of the system

47: /// </summary>

48: /// <returns></returns>

49: public static float GetDpi()

50: {

51:   using (System.Drawing.Graphics graph = System.Drawing.Graphics.FromHwnd(IntPtr.Zero))

52:   {

53:      if (graph == null)

54:      {

55:        throw new NullReferenceException("Graphics not found");

56:      }

57:  

58:     if (!graph.DpiX.Equals(graph.DpiY))

59:      {

60:        throw new ArithmeticException("DpiX != DpiY");

61:      }

62:  

63:     return graph.DpiX;

64:   }

posted @ 2011-05-26 10:47 linFen 阅读(104) 评论(0) 编辑