- Call API without parameters
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text;
namespace _031_Windows_API
{
class WinAPI
{
[DllImport(" user32", EntryPoint= "GetWindowText")]
public static extern int GetWindowText(
IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32" )]
public static extern int GetWindowTextLength(
IntPtr hWnd);
}
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string [] args)
{
Process p = new Process();
p.StartInfo.FileName = " Notepad";
p.Start();
p.WaitForInputIdle(10000 );
int length = WinAPI.GetWindowTextLength (p.MainWindowHandle) + 1;
StringBuilder caption = new StringBuilder(length);
int result =
WinAPI.GetWindowText(
p.MainWindowHandle, caption, caption.Capacity);
if (result != 0)
{
Console.Write
(" Via Windows API: The caption of the window is ");
Console.WriteLine(" <{0}>",caption);
Console.Write
( "Via Process object: the caption of the window is " );
Console.WriteLine("<{0}>" ,p.MainWindowTitle);
}
else
{
Console.WriteLine( "Either the window has no caption, ");
Console.WriteLine( "or it cannot be retrieved.");
}
//p.CloseMainWindow();
Console.ReadKey();
}
}
}
- Call Win API with Win API structs
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
/// <summary>
/// This version of InputFocus includes additional members and functional
/// upgrades of existing members.
///
/// New Members
/// -----------
/// FindWindow Windows API function exported as a public member.
///
/// WindowReady method checks for the existance of
/// a window of a given classname and caption. Window classnames can be
/// determined using Spy++ or similar utility.
///
/// Upgrades
/// --------
/// Set method checks for a zero value input parameter representing
/// the window handle before attempting to change input focus. Set also
/// polls (up to MaxAttempts times) for positive evidence that input focus
/// has been changed to the window requested.
///
/// MaxAttempts property (read/write) allows the user to control
/// the number of attempts for each polling.
///
/// SleepPollTime property (read/write) allow the user to control
/// the duration of each sleep between polling checks.
///
/// Static constructor initializes private fields for maximum number of
/// polling attempts per method call, and duration of the sleep between
/// polling attempts.
///
/// </summary>
public class InputFocus
{
[DllImport(" user32.dll")]
private static extern int GetForegroundWindow();
[DllImport("user32.dll ")]
private static extern int GetWindowText
(IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32.dll ")]
private static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll" )]
private static extern int SetForegroundWindow(IntPtr hWnd);
[DllImport( "user32.dll")]
public static extern IntPtr FindWindow( string className, string caption);
public InputFocus()
{
//
// TODO: Add constructor logic here
//
}
static InputFocus()
{
_maxAttempts = 10;
_sleepPollTime = 100 ;
}
public static bool WindowReady(string className, string caption)
{
bool result = false;
for ( int index = 0 ; index < MaxAttempts; index++)
{
if (FindWindow(className, caption) == IntPtr.Zero)
{
Console.WriteLine("Window not ready, sleeping ");
Thread.Sleep(SleepPollTime);
}
else
{
result = true ;
break;
}
}
return result;
}
public static bool Set(IntPtr hWnd)
{
bool windowInFocus = false;
Console.Write( "Attempting to set focus to window with handle ");
Console.WriteLine(hWnd);
if (hWnd != IntPtr.Zero)
{
for (int index = 0; index < MaxAttempts; index++)
{
windowInFocus = SetForegroundWindow(hWnd) != 0 ? true : false ;
if (!windowInFocus)
{
Console.WriteLine("Failed to focus. Attempt {0}" , index);
Thread.Sleep(SleepPollTime);
}
else
{
Console.WriteLine("Focus achieved");
break;
}
}
}
return windowInFocus;
}
public static bool Set(System.IntPtr hWnd, string caption)
{
return (Set(hWnd) && CurrentWindow == caption) ? true : false;
}
public static string CurrentWindow
{
get
{
IntPtr hWnd = IntPtr.Zero ;
hWnd = (IntPtr)GetForegroundWindow();
int nChars = GetWindowTextLength(hWnd) + 1;
StringBuilder caption = new StringBuilder(nChars);
if (GetWindowText(hWnd, caption, nChars) > 0)
{
return caption.ToString();
}
else
{
return null;
}
}
}
private static int _maxAttempts;
public static int MaxAttempts
{
get { return _maxAttempts; }
set { _maxAttempts = value; }
}
private static int _sleepPollTime;
public static int SleepPollTime
{
get { return _sleepPollTime; }
set { _sleepPollTime = value; }
}
}
using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace _0401_InputFocusUpgrade
{
/// <summary>
/// The purpose of this example is to show a slightly more featured
/// version of the InputFocus class and to show that the .MainWindowHandle
/// property of the MSPaint process instance is 0 running under XP Professional.
/// This does not appear to be the case under W2K Server.
///
/// Note that we have to use InputFocus.FindWindow to retrieve the handle
/// of the main window of MSPaint.
///
/// The action of this particular example is to use Alt-PrintScreen to
/// grab a bitmap of the window with input focus (in this case, Calculator)
/// and display it in Paint. This can also be accomplished using display
/// contexts (DC). For an example, see Eric Gunnerson's Win32Window class.
///
/// See the comments of the InputFocus class file in this project for more
/// information.
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string [] args)
{
const string paintFileName = "mspaint ";
const string paintCaption = "untitled - Paint ";
const string paintClassName = "MSPaintApp ";
const string calcFileName = "calc ";
Process calc = new Process();
calc.StartInfo.FileName = calcFileName;
calc.Start();
calc.WaitForInputIdle(10000);
SendKeys.SendWait(" %{PRTSC}");
Console.Write("Calc Main Window Handle via .MainWindowHandle: ");
Console.WriteLine(calc.MainWindowHandle);
calc.CloseMainWindow();
Process paint = new Process();
paint.StartInfo.FileName = paintFileName;
paint.Start();
paint.WaitForInputIdle(10000 );
if (InputFocus.WindowReady(paintClassName,paintCaption))
{
Console.Write("Paint Main Window Handle via .MainWindowHandle: " );
Console.WriteLine(paint.MainWindowHandle);
if (InputFocus.Set(InputFocus.FindWindow(paintClassName,paintCaption)))
{
SendKeys.SendWait("^v" );
}
else
{
Console.WriteLine( "Can't bring Paint to focus");
}
}
else
{
Console.WriteLine(" Paint window can't be located");
}
}
}
}
- Memory API
using System;
using System.Runtime.InteropServices;
namespace _051_Memory_API
{
//[StructLayout(LayoutKind.Sequential)]
// struct MEMORYSTATUSEX
//{
// public uint dwLength;
// public uint dwMemoryLoad;
// public ulong ullTotalPhys;
// public ulong ullAvailPhys;
// public ulong ullTotalPageFile;
// public ulong ullAvailPageFile;
// public ulong ullTotalVirtual;
// public ulong ullAvailVirtual;
// public ulong ullAvailExtendedVirtual;
//}
// Use this version of the struct to generate an error
[StructLayout(LayoutKind.Sequential )]
struct MEMORYSTATUSEX
{
public ulong dwLength;
public ulong dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
}
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
[DllImport("kernel32.dll")]
static extern bool GlobalMemoryStatusEx( ref MEMORYSTATUSEX lpBuffer);
[DllImport( "kernel32.dll")]
static extern int GetLastError();
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static unsafe void Main(string[] args)
{
MEMORYSTATUSEX m = new MEMORYSTATUSEX();
m.dwLength = ( uint)sizeof(MEMORYSTATUSEX);
bool success = GlobalMemoryStatusEx( ref m);
if (success)
{
displayMemoryData(m);
}
else
{
Console.WriteLine(" Error Number " + GetLastError());
}
Console.ReadKey();
}
private static void displayMemoryData(MEMORYSTATUSEX m)
{
Console.WriteLine ("{0,-30}{1}", "dwLength",m.dwLength);
Console.WriteLine( "{0,-30}{1}%"," dwMemoryLoad",m.dwMemoryLoad);
Console.WriteLine(" {0,-30}{1} MB","ullTotalPhys ",m.ullTotalPhys/1024/ 1024);
Console.WriteLine("{0,-30}{1} MB ","ullAvailPhys" ,m.ullAvailPhys/1024/1024 );
Console.WriteLine("{0,-30}{1} MB" ,"ullTotalPageFile",m.ullTotalPageFile /1024/1024);
Console.WriteLine("{0,-30}{1} MB", "ullAvailPageFile",m.ullAvailPageFile/ 1024/1024);
Console.WriteLine( "{0,-30}{1} MB"," ullTotalVirtual",m.ullTotalVirtual/1024 /1024);
Console.WriteLine("{0,-30}{1} MB ","ullAvailVirtual ",m.ullAvailVirtual/1024/ 1024);
Console.WriteLine("{0,-30}{1} MB ","ullAvailExtendedVirtual" ,m.ullAvailExtendedVirtual/1024/1024 );
}
}
}
using System;
using System.Runtime.InteropServices;
namespace _051_Memory_API
{
[StructLayout(LayoutKind.Sequential)]
struct MEMORYSTATUSEX
{
public uint dwLength;
public uint dwMemoryLoad;
public ulong ullTotalPhys;
public ulong ullAvailPhys;
public ulong ullTotalPageFile;
public ulong ullAvailPageFile;
public ulong ullTotalVirtual;
public ulong ullAvailVirtual;
public ulong ullAvailExtendedVirtual;
}
// Use this version of the struct to generate an error
//[StructLayout(LayoutKind.Sequential )]
//struct MEMORYSTATUSEX
//{
// public ulong dwLength;
// public ulong dwMemoryLoad;
// public ulong ullTotalPhys;
// public ulong ullAvailPhys;
// public ulong ullTotalPageFile;
// public ulong ullAvailPageFile;
// public ulong ullTotalVirtual;
// public ulong ullAvailVirtual;
// public ulong ullAvailExtendedVirtual;
//}
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GlobalMemoryStatusEx( ref MEMORYSTATUSEX lpBuffer);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
MEMORYSTATUSEX m = new MEMORYSTATUSEX();
m.dwLength = (uint)Marshal.SizeOf(m);
bool success = GlobalMemoryStatusEx(ref m);
if (success)
{
displayMemoryData(m);
}
else
{
Console.WriteLine("Error Number " + Marshal.GetLastWin32Error());
}
}
private static void displayMemoryData(MEMORYSTATUSEX m)
{
Console.WriteLine("{0,-30}{1} ","dwLength ",m.dwLength);
Console.WriteLine("{0,-30}{1}% ","dwMemoryLoad" ,m.dwMemoryLoad);
Console.WriteLine("{0,-30}{1} MB" ,"ullTotalPhys",m.ullTotalPhys /1024/1024);
Console.WriteLine( "{0,-30}{1} MB", "ullAvailPhys",m.ullAvailPhys/ 1024/1024);
Console.WriteLine(" {0,-30}{1} MB"," ullTotalPageFile",m.ullTotalPageFile/1024 /1024);
Console.WriteLine(" {0,-30}{1} MB","ullAvailPageFile ",m.ullAvailPageFile/1024/ 1024);
Console.WriteLine("{0,-30}{1} MB ","ullTotalVirtual" ,m.ullTotalVirtual/1024/1024 );
Console.WriteLine("{0,-30}{1} MB" ,"ullAvailVirtual",m.ullAvailVirtual /1024/1024);
Console.WriteLine("{0,-30}{1} MB", "ullAvailExtendedVirtual",m.ullAvailExtendedVirtual /1024/1024);
}
}
}
- Windows Message
using System;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Windows.Forms;
using System.Text;
using System.Collections;
namespace _045_ID_Oracle
{
public enum windowTypes : uint
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1 ,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6,
};
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
[DllImport("user32.dll ")]
private static extern IntPtr GetWindow(IntPtr hWnd, windowTypes uCmd);
[ DllImport(" user32.dll") ]
private static extern IntPtr GetNextDlgTabItem
(IntPtr hDlg, IntPtr hCtl, bool bPrevious);
[ DllImport("user32.dll ") ]
public static extern int GetDlgCtrlID(IntPtr hCtl);
[ DllImport("user32.dll" ) ]
private static extern int GetDlgItemText(
IntPtr hDlg,
int nIDDlgItem,
StringBuilder lpString,
int nMaxCount
);
[ DllImport("user32.dll") ]
private static extern IntPtr GetNextDlgGroupItem
(IntPtr hDlg, IntPtr hCtl, bool bPrevious);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string [] args)
{
string formatter = "{0,-20}{1,-20:x}{2,-20}";
Process np = new Process();
np.StartInfo.FileName = "notepad ";
np.Start();
np.WaitForInputIdle(10000);
SendKeys.SendWait("dummy text");
np.WaitForInputIdle(10000);
SendKeys.SendWait(" ^f");
np.WaitForInputIdle(10000);
SendKeys.SendWait("dummy text" );
IntPtr hDlg =
GetWindow(np.MainWindowHandle, windowTypes.GW_ENABLEDPOPUP);
ArrayList controlHandles = new ArrayList();
if (hDlg != IntPtr.Zero)
{
IntPtr firstHandle = GetNextDlgTabItem(hDlg, IntPtr.Zero, false);
IntPtr hCtl = firstHandle;
if (hCtl != IntPtr.Zero)
{
// tab through all of the controls on this form
do
{
// if this control is part of a group, get all the group members
IntPtr subHandle = hCtl;
do
{
// don't allow duplicates
if ( !controlHandles.Contains(hCtl))
{
controlHandles.Add(hCtl);
}
hCtl = GetNextDlgGroupItem(hDlg, hCtl, false);
} while (hCtl != subHandle);
hCtl = GetNextDlgTabItem(hDlg, hCtl, false);
} while (hCtl != firstHandle);
Console.WriteLine(formatter,"Caption", "ID value in Hex", "Handle");
Console.WriteLine(formatter, "-------"," ---------------","------ ");
StringBuilder caption = new StringBuilder();
foreach (IntPtr handle in controlHandles)
{
int Id = GetDlgCtrlID(handle);
GetDlgItemText(hDlg, Id, caption, caption.Capacity);
Console.WriteLine(formatter,caption,Id,handle);
}
}
}
Console.ReadKey();
np.Kill();
}
}
}
using System;
using System.Diagnostics;
using System.Windows.Forms ;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
namespace CheckBoxWithID
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
public enum ids : int {
MatchCase = 0x411, Direction = 0x430 , Up = 0x420, Down = 0x421}
public enum windowTypes : uint
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4 ,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6,
};
[DllImport( "user32.dll")]
private static extern int SendNotifyMessage(
IntPtr hWndControl, // handle to destination control
uint msg, // message ID
IntPtr wParam, // checkbox/radio button state
StringBuilder lParam); // pointer to null terminated string
[DllImport( "user32.dll")]
private static extern int PostMessage(
IntPtr hWndControl, // handle to destination control
uint msg, // message ID
IntPtr wParam, // checkbox/radio button state
StringBuilder lParam); // pointer to null terminated string
private const uint BM_SETCHECK = 0x00F1;
private const int BST_CHECKED = 0x0001;
private const int BST_UNCHECKED = 0x0000;
public enum buttonSetting : int {on = BST_CHECKED, off = BST_UNCHECKED}
[ DllImport( "user32.dll") ]
private static extern IntPtr GetWindow (IntPtr hWnd, windowTypes uCmd);
[ DllImport("user32.dll" ) ]
public static extern IntPtr GetDlgItem(
IntPtr hDlg, int nIDDlgItem );
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string [] args)
{
Process p = new Process();
p.StartInfo.FileName = " notepad";
p.Start();
p.WaitForInputIdle(10000 );
SendKeys.SendWait("Dummy Input" );
p.WaitForInputIdle(10000);
SendKeys.SendWait(" ^f");
p.WaitForInputIdle(10000);
IntPtr hDlg = GetWindow(
p.MainWindowHandle, windowTypes.GW_ENABLEDPOPUP);
IntPtr hCtl = GetDlgItem(hDlg, (int)ids.MatchCase);
RadioAndCheckBoxClick(hCtl, buttonSetting.on);
hCtl = GetDlgItem(hDlg, (int )ids.Up);
RadioAndCheckBoxClick(hCtl, buttonSetting.on);
hCtl = GetDlgItem(hDlg, ( int)ids.Down);
RadioAndCheckBoxClick(hCtl, buttonSetting.off);
Thread.Sleep(5000 );
Console.ReadKey();
p.Kill();
}
public static void RadioAndCheckBoxClick(IntPtr hCtl, buttonSetting state)
{
PostMessage( // SendNotifyMessage will work too
hCtl, // handle to destination control
BM_SETCHECK, // message ID
(IntPtr)state, // new state for radio button or checkbox
null // = 0; not used, must be zero
);
}
}
}
using System;
using System.Diagnostics;
using System.Runtime.InteropServices ;
using System.Windows.Forms;
using System.Text ;
using System.Threading;
using System.Collections ;
namespace _043_Button_Click
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
const int NORMALWAIT = 10000;
Process p = new Process();
p.StartInfo.FileName = "notepad";
p.Start();
p.WaitForInputIdle(NORMALWAIT);
// Bring up the "About Notepad Window"
SendKeys.SendWait("%h");
p.WaitForInputIdle(NORMALWAIT);
SendKeys.SendWait("a" );
p.WaitForInputIdle(NORMALWAIT);
// Locate the Dialog Box
IntPtr hWnd =
ControlHelper.GetDialog(p.MainWindowHandle, "About Notepad");
if (hWnd != IntPtr.Zero)
{
// locate the OK button, and click it
IntPtr buttonHwnd = ControlHelper.GetItemHandleByCaption(hWnd,"OK" );
if (buttonHwnd != IntPtr.Zero)
{
Console.WriteLine("About to click the OK button ");
Thread.Sleep(NORMALWAIT/3 );
ControlHelper.ButtonClick(buttonHwnd);
Console.WriteLine("Done clicking the OK button ");
Thread.Sleep(NORMALWAIT/ 3);
}
}
p.CloseMainWindow();
}
}
class ControlHelper
{
// Enumeration used to find the dialog box
public enum windowTypes : uint
{
GW_HWNDFIRST = 0 ,
GW_HWNDLAST = 1 ,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_ENABLEDPOPUP = 6,
}
// Constant value representing the Button Click Windows Message
private const uint BM_CLICK = 0x00F5;
// Windows API DLL Imports
[DllImport("user32.dll ")]
private static extern IntPtr GetWindow(IntPtr hWnd, windowTypes uCmd);
[DllImport(" user32", EntryPoint= "GetWindowText")]
public static extern int GetWindowText(
IntPtr hWnd, StringBuilder text, int count);
[DllImport("user32" )]
public static extern int GetWindowTextLength(
IntPtr hWnd);
[ DllImport( "user32.dll") ]
public static extern int GetDlgCtrlID(IntPtr hwndCtl);
[ DllImport(" user32.dll") ]
private static extern IntPtr GetNextDlgTabItem
(IntPtr hDlg, IntPtr hCtl, bool bPrevious);
[ DllImport("user32.dll ") ]
private static extern int GetDlgItemText(
IntPtr hDlg,
int nIDDlgItem,
StringBuilder lpString,
int nMaxCount
);
[DllImport("user32.dll ")]
private static extern int SendNotifyMessage(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
StringBuilder lParam
);
[DllImport( "user32.dll")]
private static extern int PostMessage(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
StringBuilder lParam
);
[ DllImport("user32.dll ") ]
private static extern IntPtr GetNextDlgGroupItem
(IntPtr hDlg, IntPtr hCtl, bool bPrevious);
// Wrappers for the Windows API functions
public static string WindowCaption(IntPtr hWnd)
{
int length = GetWindowTextLength(hWnd) + 1;
StringBuilder caption = new StringBuilder(length);
int result =
GetWindowText(hWnd, caption, caption.Capacity);
if (result != 0)
{
return caption.ToString();
}
else
{
return "";
}
}
public static IntPtr GetDialog(IntPtr hWnd, string caption)
{
IntPtr hDlg = GetWindow(hWnd, windowTypes.GW_ENABLEDPOPUP);
if (hDlg != IntPtr.Zero && WindowCaption(hDlg) == caption)
{
return hDlg;
}
else
{
return IntPtr.Zero;
}
}
// Get all of the control handles on the designed dialog box
public static IntPtr[] GetHandles(IntPtr hDlg)
{
ArrayList controlHandles = new ArrayList();
IntPtr[] result = null ;
if (hDlg != IntPtr.Zero)
{
IntPtr firstHandle = GetNextDlgTabItem(hDlg, IntPtr.Zero, false );
IntPtr hCtl = firstHandle;
if (hCtl != IntPtr.Zero)
{
// tab through all of the controls on this form
do
{
// if this control is part of a group, get all the group members
IntPtr subHandle = hCtl;
do
{
// don't allow duplicates
if ( !controlHandles.Contains(hCtl))
{
controlHandles.Add(hCtl);
}
hCtl = GetNextDlgGroupItem(hDlg, hCtl, false);
} while (hCtl != subHandle);
hCtl = GetNextDlgTabItem(hDlg, hCtl, false);
} while (hCtl != firstHandle);
}
result = (IntPtr[])controlHandles.ToArray(typeof (IntPtr));
}
return result;
}
public static IntPtr GetItemHandleByCaption(IntPtr hDlg, string caption)
{
IntPtr[] itemHandlesArray = GetHandles(hDlg);
IntPtr result = IntPtr.Zero;
foreach (IntPtr hCtl in itemHandlesArray)
{
string s = GetDialogItemText(hDlg,hCtl);
if (caption == s)
{
result = hCtl;
break;
}
}
return result;
}
public static string GetDialogItemText(IntPtr hDlg, IntPtr hCtl)
{
StringBuilder lpString = new StringBuilder();
int id = GetDlgCtrlID(hCtl);
GetDlgItemText(hDlg, id, lpString, lpString.Capacity );
return lpString.ToString();
}
public static void ButtonClick(IntPtr hCtl)
{
PostMessage( // SendNotifyMessage will work too
hCtl, // handle to destination control
BM_CLICK, // message ID
IntPtr.Zero, // = 0; not used, must be zero
null // = 0; not used, must be zero
);
}
}
}
--
Happy day, happy life!
No comments:
Post a Comment