《软件测试自动化之道》读书笔记 之 基于Windows的UI测试

2014-09-25

测试自动化程序的任务
待测程序
测试程序
  启动待测程序
  获得待测程序主窗体的句柄
  获得有名字控件的句柄
  获得无名字控件的句柄
  发送字符给控件
  鼠标单击一个控件
  处理消息对话框
  处理菜单
  检查应用程序状态
  示例程序
参考

本章主要讲述如何使用底层的Windows自动化技术通过用户界面来测试应用程序。这些技术涉及Win32 API的调用(比如FindWindow()函数)以及想待测程序发送Windows消息(比如WM_LBUTTONUP)。

测试自动化程序的任务


返回

基于Windows的UI测试,逍遥完成的工作主要有以下三类:

  • 找到目标窗体/控件的句柄
  • 操作这个窗体/控件
  • 检测这个窗体/控件

待测程序


返回

待测程序是一个用来做颜色混合的应用程序,关键代码如下:

 1 using System;
2 using System.Windows.Forms;
3
4 namespace WinApp
5 {
6 public partial class Form1 : Form
7 {
8 public Form1()
9 {
10 InitializeComponent();
11 }
12
13 private void button1_Click(object sender, EventArgs e)
14 {
15 string tb = textBox1.Text;
16 string cb = comboBox1.Text;
17
18 if (tb == "<enter color>" || cb == "<pick>")
19 MessageBox.Show("You need 2 colors", "Error");
20 else
21 {
22 if (tb == cb)
23 listBox1.Items.Add("Result is " + tb);
24 else if (tb == "red" && cb == "blue" || tb == "blue" && cb == "red")
25 listBox1.Items.Add("Result is purple");
26 else
27 listBox1.Items.Add("Result is black");
28 }
29 }
30
31 private void exitToolStripMenuItem1_Click(object sender, EventArgs e)
32 {
33 this.Close();
34 }
35 }
36 }

 图1 AUT 

测试程序


返回

启动待测程序

 using System.Diagnostics;

         static void Main(string[] args)
{
//...
string path = "..\\..\\..\\WinApp\\bin\\Debug\\WinApp.exe";
Process p = Process.Start(path);
//...
}

获得待测程序主窗体的句柄

要获得待测程序主窗体的句柄,可使用FindWindow() Win32 API函数来解决这个问题。

FindWindow()的函数签名用C++来描述是:

HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName); 

Win32 API函数FindWindow()是Windows操作系统的一部分,是由传统的C++程序而不是用受控代码(managed code)编写的。它返回HWND(Handle of Window), 是一个窗体的句柄

C#要使用Win32 API函数FindWindow(),可通过.Net平台的invoke(P/Invoke)机制,P/Invoke相关特性位于System.Runtime.InteropServices命名空间内。

        [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

DllImport是用来将特性化方法由非托管动态链接库 (DLL) 作为静态入口点公开。

说明[1]
  1、DllImport只能放置在方法声明上。
  2、DllImport具有单个定位参数:指定包含被导入方法的 dll 名称的 dllName 参数。
  3、DllImport具有五个命名参数:
   a、CallingConvention 参数指示入口点的调用约定。如果未指定 CallingConvention,则使用默认值 CallingConvention.Winapi。
   b、CharSet 参数指示用在入口点中的字符集。如果未指定 CharSet,则使用默认值 CharSet.Auto。
   c、EntryPoint 参数给出 dll 中入口点的名称。如果未指定 EntryPoint,则使用方法本身的名称。
   d、ExactSpelling 参数指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配。如果未指定 ExactSpelling,则使用默认值 false。
   e、PreserveSig 参数指示方法的签名应当被保留还是被转换。当签名被转换时,它被转换为一个具有 HRESULT 返回值和该返回值的一个名为 retval 的附加输出参数的签名。如果未指定 PreserveSig,则使用默认值 true。
   f、SetLastError 参数指示方法是否保留 Win32"上一错误"。如果未指定 SetLastError,则使用默认值 false。
  4、它是一次性属性类。
  5、此外,用 DllImport 属性修饰的方法必须具有 extern 修饰符。

注意:.NET平台大大简化了数据类型的模型,从而提高了程序开发的效率。要使用P/Invoke机制,必须为Win32数据类型找到相应的 C#数据类型。

此示例中,在.NET坏境中,窗体句柄的类型是System.IntPtr,它是一个平台相关的类型,用来代表指针(内存地址)或者句柄。它对应于Win32的数据类型HWND。String对应于LPCTSTR。

C#与win32 api数据类型对照

参数:

  • lpClassName:是窗体类名称,更OOP中类无任何关系。是由系统生成的一个字符串,用来把相应的窗体注册到操作系统。因为窗体/控件类的名称并不具有唯一性,对查找一个窗体/控件并没有太大帮助。因此,在本示例中传给它null。
  • lpWindowName:是窗体的名称。也被叫做window title或者window caption。在windows form程序中,这个值通常称为form name。

获得待测程序主窗体的句柄示例代码如下:

 using System.Runtime.InteropServices;

         static void Main(string[] args)
{
//...
IntPtr mwh = FindMainWindowHandle("Form1", , );
//...
} [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
{
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
{
IntPtr mwh = IntPtr.Zero;
bool formFound = false;
int attempts = ; do
{
//FindWindow
mwh = FindWindow(null, caption);
if (mwh == IntPtr.Zero)
{
Console.WriteLine("Form not yet found");
Thread.Sleep(delay);
++attempts;
}
else
{
Console.WriteLine("Form has been found");
formFound = true;
}
} while (!formFound && attempts < maxTries); if (mwh != IntPtr.Zero)
return mwh;
else
throw new Exception("Could not find Main Window");
}

获得有名字控件的句柄

         static void Main(string[] args)
{
//...
IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
//...
} [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

参数:

  • hwndParent:控件目标的父窗体句柄
  • hwndChildAfter:从哪个控件开始找,即从下一个控件开始找
  • lpszClass:class name(如上参数lpClassName)
  • lpszWindow:目标控件的window name/title/caption

图2 Spy++捕获控件button1  

获得无名字控件的句柄

如何获得一个没有名字的空间的句柄,可通过隐含索引来查找相应控件

         static void Main(string[] args)
{
//...
IntPtr cb = FindWindowByIndex(mwh, );
//...
} [DllImport("user32.dll", EntryPoint = "FindWindowEx",CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
{
if (index == )
return hwndParent;
else
{
int ct = ;
IntPtr result = IntPtr.Zero;
do
{
//FindWindowEx
result = FindWindowEx(hwndParent, result, null, null);
if (result != IntPtr.Zero)
++ct;
} while (ct < index && result != IntPtr.Zero); return result;
}
}

注意:这里索引的顺序是加入主窗体的顺序。见如下代码,主窗体的索引值为0,先后加入的控件button1的索引为1,comboBox1的索引为2,...

         private void InitializeComponent()
{
//...
this.Controls.Add(this.button1);
this.Controls.Add(this.comboBox1);
this.Controls.Add(this.textBox1);
this.Controls.Add(this.menuStrip1);
this.Controls.Add(this.listBox1);
//...
}

你也可以通过工具“spy++”来查找先后。

发送字符给控件

SendMessage()的函数签名用C++签名如下:

LRESULT SendMessage(HWND HwND, UINT Msg, WPARAM wParam, LPARAM lParam); 
  • HwND:目标窗体/控件的句柄
  • Msg:要发给该控件的Window消息
  • wParam, lParam:它们的意思和数据类型取决于相应的Windows消息

本例中,我们要发送一个VM_CHAR消息。当按键按下时,VM_CHAR消息会发送给拥有键盘焦点的那个控件。实际上,VM_CHAR是一个Windows的常量符号,它定义为0x0102。wParam参数指定的是被按下按键的字符代码。lParam参数指定的是不同的按键状态码,比如重复次数、扫描码等。有了这些信息,就可以创建相应的C#签名:

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

发送字符给控件的示例代码:

         static void Main(string[] args)
{
//...
SendChars(tb, "red");
//...
} static void SendChars(IntPtr hControl, string s)
{
foreach (char c in s)
{
SendChar(hControl, c);
}
} static void SendChar(IntPtr hControl, char c)
{
uint WM_CHAR = 0x0102;
SendMessage1(hControl, WM_CHAR, c, );
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

鼠标单击一个控件

PostMessage()的函数签名用C++签名如下:

BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM LParam);

PostMessage()和SendMessage()的参数列表完全一致,他们的不同是:SendMessage()会等相应的Windows消息之后才会返回;PostMessage()不会。
相应的C#签名:

        [DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

鼠标单击一个控件的示例代码:

         static void Main(string[] args)
{
//...
ClickOn(okButt);
//...
} static void ClickOn(IntPtr hControl)
{
uint WM_LBUTTONDOWN = 0x0201;
uint WM_LBUTTONUP = 0x0202;
PostMessage1(hControl, WM_LBUTTONDOWN, , );
PostMessage1(hControl, WM_LBUTTONUP, , );
}
[DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam);

处理消息对话框

消息对话框是一个上层(top-level)窗体,使用FindWindow()函数捕获它。

处理菜单

处理菜单的的示例代码:

         static void Main(string[] args)
{
//... //mwh: main window handle
IntPtr hMainMenu = GetMenu(mwh);
IntPtr hFile = GetSubMenu(hMainMenu, );
int iExit = GetMenuItemID(hFile, );
uint WM_COMMAND = 0x0111;
SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero);
//...
} [DllImport("user32.dll")] //
static extern IntPtr GetMenu(IntPtr hWnd); [DllImport("user32.dll")] //
static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); [DllImport("user32.dll")] //
static extern int GetMenuItemID(IntPtr hMenu, int nPos); [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam);
  • GetMenu():返回程序主菜单的句柄
  • GetSubMenu():返回子菜单的句柄
  • GetSubMenu():返回菜单项的索引值。

在该示例中选择File->Exit,并点击它。

图3 处理菜单

检查应用程序状态

使用VM_GETTEXT和SendMessage()获得控件状态

检查应用程序状态的示例代码

         static void Main(string[] args)
{
//...
uint VM_GETTEXT = 0x000D;
byte[] buffer=new byte[];
string text = null;
int numFetched = SendMessage3(tb, VM_GETTEXT, , buffer);
text = System.Text.Encoding.Unicode.GetString(buffer);
Console.WriteLine("Fetched " + numFetched + " chars");
Console.WriteLine("TextBox1 contains = " + text);
//...
}
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam);

示例程序

 // Chapter 3 - Windows-Based UI Testing
// Example Program: WindowsUITest using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading; namespace WindowsUITest
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
try
{
Console.WriteLine("\nLaunching application under test"); string path = "..\\..\\..\\WinApp\\bin\\Debug\\WinApp.exe";
Process p = Process.Start(path); Console.WriteLine("\nFinding main window handle");
IntPtr mwh = FindMainWindowHandle("Form1", , );
Console.WriteLine("Main window handle = " + mwh); Console.WriteLine("\nFinding handles to textBox1, comboBox1");
Console.WriteLine(" button1, listBox1"); // you may want to add delays here to make sure Form has rendered
IntPtr tb = FindWindowEx(mwh, IntPtr.Zero, null, "<enter color>");
IntPtr cb = FindWindowByIndex(mwh, );
IntPtr butt = FindWindowEx(mwh, IntPtr.Zero, null, "button1");
IntPtr lb = FindWindowByIndex(mwh, ); if (tb == IntPtr.Zero || cb == IntPtr.Zero ||
butt == IntPtr.Zero || lb == IntPtr.Zero)
throw new Exception("Unable to find all controls");
else
Console.WriteLine("All control handles found"); Console.WriteLine("\nClicking button1");
ClickOn(butt); Console.WriteLine("Clicking away Error message box");
IntPtr mb = FindMessageBox("Error");
if (mb == IntPtr.Zero)
throw new Exception("Unable to find message box");
IntPtr okButt = FindWindowEx(mb, IntPtr.Zero, null, "OK");
if (okButt == IntPtr.Zero)
throw new Exception("Unable to find OK button");
ClickOn(okButt); Console.WriteLine("Typing 'red' and 'blue' to application");
SendChars(tb, "red"); Console.WriteLine("Check for textBox1");
uint VM_GETTEXT = 0x000D;
byte[] buffer = new byte[];
string text = null;
int numFetched = SendMessage3(lb, VM_GETTEXT, , buffer);
text = System.Text.Encoding.Unicode.GetString(buffer);
Console.WriteLine("Fetched " + numFetched + " chars");
Console.WriteLine("TextBox1 contains = " + text); ClickOn(cb);
SendChars(cb, "blue"); Console.WriteLine("Clicking on button1");
ClickOn(butt); Console.WriteLine("\nChecking listBox1 for 'purple'"); uint LB_FINDSTRING = 0x018F;
int result = SendMessage4(lb, LB_FINDSTRING, -, "Result is purple1");
if (result >= )
Console.WriteLine("\nTest scenario result = Pass");
else
Console.WriteLine("\nTest scenario result = *FAIL*"); Console.WriteLine("\nExiting app in 3 seconds . . . ");
//GetMenu not work
Thread.Sleep();
IntPtr hMainMenu = GetMenu(mwh);
IntPtr hFile = GetSubMenu(hMainMenu, );
int iExit = GetMenuItemID(hFile, );
uint WM_COMMAND = 0x0111;
SendMessage2(mwh, WM_COMMAND, iExit, IntPtr.Zero); Console.WriteLine("\nDone");
Console.ReadLine();
}
catch (Exception ex)
{
Console.WriteLine("Fatal error: " + ex.Message);
}
} // Main() static IntPtr FindTopLevelWindow(string caption, int delay, int maxTries)
{
IntPtr mwh = IntPtr.Zero;
bool formFound = false;
int attempts = ; do
{
mwh = FindWindow(null, caption);
if (mwh == IntPtr.Zero)
{
Console.WriteLine("Form not yet found");
Thread.Sleep(delay);
++attempts;
}
else
{
Console.WriteLine("Form has been found");
formFound = true;
}
} while (!formFound && attempts < maxTries); if (mwh != IntPtr.Zero)
return mwh;
else
throw new Exception("Could not find Main Window");
} // FindTopLevelWindow() static IntPtr FindMainWindowHandle(string caption, int delay, int maxTries)
{
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindMessageBox(string caption)
{
int delay = ;
int maxTries = ;
return FindTopLevelWindow(caption, delay, maxTries);
} static IntPtr FindWindowByIndex(IntPtr hwndParent, int index)
{
if (index == )
return hwndParent;
else
{
int ct = ;
IntPtr result = IntPtr.Zero;
do
{
result = FindWindowEx(hwndParent, result, null, null);
if (result != IntPtr.Zero)
++ct;
} while (ct < index && result != IntPtr.Zero); return result;
}
} // FindWindowByIndex() static void ClickOn(IntPtr hControl)
{
uint WM_LBUTTONDOWN = 0x0201;
uint WM_LBUTTONUP = 0x0202;
PostMessage1(hControl, WM_LBUTTONDOWN, , );
PostMessage1(hControl, WM_LBUTTONUP, , );
Thread.Sleep();
} static void SendChar(IntPtr hControl, char c)
{
uint WM_CHAR = 0x0102;
SendMessage1(hControl, WM_CHAR, c, );
} static void SendChars(IntPtr hControl, string s)
{
foreach (char c in s)
{
SendChar(hControl, c);
}
} // P/Invoke Aliases [DllImport("user32.dll", EntryPoint = "FindWindow", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Auto)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow); // for WM_CHAR message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam); // for WM_COMMAND message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern void SendMessage2(IntPtr hWnd, uint Msg, int wParam, IntPtr lParam); // for WM_LBUTTONDOWN and WM_LBUTTONUP messages
[DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg, int wParam, int lParam); // for WM_GETTEXT message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage3(IntPtr hWndControl, uint Msg, int wParam, byte[] lParam); // for LB_FINDSTRING message
[DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Auto)]
static extern int SendMessage4(IntPtr hWnd, uint Msg, int wParam, string lParam); // Menu routines
[DllImport("user32.dll")] //
static extern IntPtr GetMenu(IntPtr hWnd); [DllImport("user32.dll")] //
static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); [DllImport("user32.dll")] //
static extern int GetMenuItemID(IntPtr hMenu, int nPos); } // class
} // ns

参考

[1] C# DllImport的用法

[2] C#与win32 api数据类型对照

《软件测试自动化之道》读书笔记 之 基于Windows的UI测试的更多相关文章

  1. 《软件测试自动化之道》读书笔记 之 基于反射的UI测试

    <软件测试自动化之道>读书笔记 之 基于反射的UI测试 2014-09-24 测试自动化程序的任务待测程序测试程序  启动待测程序  设置窗体的属性  获取窗体的属性  设置控件的属性  ...

  2. 《软件测试自动化之道》读书笔记 之 底层的Web UI 测试

    <软件测试自动化之道>读书笔记 之 底层的Web UI 测试 2014-09-28 测试自动化程序的任务待测程序测试程序  启动IE并连接到这个实例  如何判断待测web程序完全加载到浏览 ...

  3. 《软件测试自动化之道》读书笔记 之 SQL 存储过程测试

    <软件测试自动化之道>读书笔记 之 SQL 存储过程测试 2014-09-28 待测程序测试程序   创建测试用例以及测试结果存储  执行T-SQL脚本  使用BCP工具导入测试用例数据  ...

  4. 《软件测试自动化之道》读书笔记 之 XML测试

    <软件测试自动化之道>读书笔记 之 XML测试 2014-10-07 待测程序测试程序  通过XmlTextReader解析XML  通过XmlDocument解析XML  通过XmlPa ...

  5. 《Essential C++》读书笔记 之 基于对象编程风格

    <Essential C++>读书笔记 之 基于对象编程风格 2014-07-13 4.1 如何实现一个class 4.2 什么是Constructors(构造函数)和Destructor ...

  6. 【哲学角度看软件测试】要想软件“一想之美”,UI 测试少不了

    摘要:软件测试的最高层次需求是:UI测试,也就是这个软件"长得好不好看". 为了让读者更好地理解测试,我们从最基础的概念开始介绍.以一个软件的"轮回"为例,下图 ...

  7. <<google软件测试之道>>读书笔记

    以前一直从开发的角度来看待测试,看完这本书以后感觉错了,难怪之前公司的测试一直搭建不起来 1.开发人员,开发测试人员,测试人员 * 开发人员负责开发 * 开发测试人员近距离接触代码,负责编写测试用例, ...

  8. 图论——读书笔记(基于BFS广度优先算法的广度优先树)

    广度优先树 对于一个图G=(V,E)在跑过BFS算法的过程中会创建一棵广度优先树. 形式化一点的表示该广度 优先树的形成过程是这样的: 对于图G=(V,E)是有向图或是无向图, 和图中的源结点s, 我 ...

  9. <微软的软件测试之道>读书笔记3

    一.自动化的标准步骤: 1.环境初始化,并检查环境是否处于正确的状态,能否开始测试 2.执行步骤 3.判断结果,并将结果保存到其它地方以供检查分析 4.环境清理,清理本用例产生的垃圾(临时文件.环境变 ...

随机推荐

  1. 二分搜索-HihoCoder1128

    题目链接:https://hihocoder.com/problemset/problem/1128 题目描述: 题目大意就是要我们编程找出K在数组a中的大小排序后的位置. 代码实现: #includ ...

  2. POJ1006 Biorhythms【中国剩余定理】

    <题目链接> 题目大意: 人体的体力每23天会达到峰值,情感每28天会达到峰值,智力每33天会达到峰值,一个人在a天体力达到峰值,b天情感达到峰值,c天智力达到峰值,求这个人下一次体力情感 ...

  3. golang 反向代理

    服务器 package main import ( "bytes" "encoding/base64" "encoding/json" &q ...

  4. Project_Lemon测评系统使用经验

    Project_Lemon使用经验 如果您还没有安装Project_Lemon,那么请移步https://www.cnblogs.com/CreeperLKF/p/9201859.html去查阅安装教 ...

  5. C# 开发圆角控件的具体实现

    http://www.jb51.net/article/47433.htm 代码来源

  6. BZOJ.4298.[ONTAK2015]Bajtocja(Hash 启发式合并)

    题目链接 \(Description\) 给定\(d\)张无向图,每张图都有\(n\)个点.一开始,在任何一张图中都没有任何边. 接下来有\(m\)次操作,每次操作会给出\(a,b,k\),意为在第\ ...

  7. Python3内置函数——reversed() = 翻转我的世界

    认识reversed单词 reversed 英[rɪ'vɜ:st] 美[rɪ'vɜst] adj. 颠倒的:相反的:(判决等)撤销的 v. 颠倒(reverse的过去式和过去分词):翻转 help(r ...

  8. [BZOJ5427]最长上升子序列/[BZOJ4282]慎二的随机数列

    [BZOJ5427]最长上升子序列/[BZOJ4282]慎二的随机数列 题目大意: 给你一个长度为\(n(n\le10^5)\)的整数序列,其中有一些数已经模糊不清了,现在请你任意确定这些整数的值,使 ...

  9. GoDaddy账户间域名转移PUSH以及ACCEPT接受域名过户方法

    GoDaddy账户之间的域名进行过户PUSH.以及接受ACCEPT一般发生在我们有要求代购.交易域名账户之间的处理.一般的海外域名注册商账户之间是直接可以用户交易过户的,不需要经过商家允许,但是不同的 ...

  10. android:单位和尺寸

    为了要让程序拥有更好的屏幕适配能力,在指定控件和布局大小的时候 最好使用 match_parent 和 wrap_content,尽量避免将控件的宽和高设定一个固定值.不过在 有些情况下,仅仅使用 m ...