山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他MessageBox(高DPI対応)
概要
メッセージボックスを使用するプログラムです。
簡単なメッセージやYes・No等の入力等もできます。
複数のプッシュボタンを使用しているときはどのボタンをディフォルトボタンとするか指定できます。
ディフォルトボタンとはメッセージボックスが表示されているときに選択状態となっているボタンです。
本プログラムはVistaからサポートしている高DPIに対応するため複雑ことを行っています。
マニフェストでの高DPIの宣言は不要です。
Windows 10での実行例
Windows 7での実行例
Windowsで自動的にスケーリング | プログラムでスケーリング |
テスト環境
コンパイラ
Visual C++ 2008 Standard 32/64bitVisual C++ 2013 Express 32/64bit
動作確認
Windows 7Windows 8.1
Windows 10 Version 1803
プログラムソースの概要
もしWindows 10 Version 1703以降のみ対象とする場合は、WIN10VERDEFの定義を1703にすれば殆どの行がコンパイルの対象とならなくなります。
MessageBox API関数を呼び出しメッセージボックスを表示します。
MessageBox API関数の戻り値をチェックします。
高DPIに対応するために以下の処理をしています。
バージョンが1607の場合は、非クライアント領域の大きさを変更させるためWM_NCCREATEメッセージ発生時にEnableNonClientDpiScalingを呼び出します。
バージョンが1703以降の場合は、SetThreadDpiAwarenessContext APIでDPI_AWARENESS_PER_MONITOR_AWARE_V2を指定することにより Windowsに非クライアント領域の大きさの変更を自動にさせます。
事前にSetProcessDPIAware API(Windows Vista以降)を実行するとプログラム自身がスケーリング処理をすることになり正常なDPI値が返されます。実行しない場合、常に96DPIが返されます。Windows XPではこのAPIは存在しないので96DPIと仮定します。なおAPIが存在するかどうかは動的にライブラリをロードし関数のエントリポイントが存在するかどうかで判断しています。
DPIはGetDpiForMonitor API(Windows 8.1以降)が使える場合はこの関数でウィンドウが属するモニターのDPIを、使えない場合GetDeviceCaps(hdc,LOGPIXELSX)によりディスクトップのDPIを取得します。
サブクラス化によりメッセージボックスのウィンドウプロシージャがメッセージを処理する前にMsgBoxSubclassProcへ先に制御がわたるようにSetWindowSubclass関数を実行します。
スケールからダイアログボックスの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
各コントロールウィンドウをEnumChildWindowsで列挙します。
スケールからコントロールの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
コントロールのフォントをWM_SETTEXTメッセージにより変更します。
YogaPro3では横が13、縦が31ピクセルが取得されました。
詳細は、Windows 10のバージョン番号をRtlGetVersionのビルド番号から取得するを参照してください。
_tWinMain関数
Windowsから最初に_tWinMain関数が呼び出されます。MessageBox API関数を呼び出しメッセージボックスを表示します。
MessageBox API関数の戻り値をチェックします。
高DPIに対応するために以下の処理をしています。
SetThreadDpiAwarenessContext
Windows 10 Version 1703より古い場合、プログラムの初期時にSetThreadDpiAwarenessContext APIが存在するかチェックし存在する場合、Windows10のバージョンをチェックします。バージョンが1607の場合は、非クライアント領域の大きさを変更させるためWM_NCCREATEメッセージ発生時にEnableNonClientDpiScalingを呼び出します。
バージョンが1703以降の場合は、SetThreadDpiAwarenessContext APIでDPI_AWARENESS_PER_MONITOR_AWARE_V2を指定することにより Windowsに非クライアント領域の大きさの変更を自動にさせます。
SetProcessDPIAware
SetThreadDpiAwarenessContext APIが使えない場合、SetProcessDPIAware API(Windows Vista以降)が使えるか確認します。事前にSetProcessDPIAware API(Windows Vista以降)を実行するとプログラム自身がスケーリング処理をすることになり正常なDPI値が返されます。実行しない場合、常に96DPIが返されます。Windows XPではこのAPIは存在しないので96DPIと仮定します。なおAPIが存在するかどうかは動的にライブラリをロードし関数のエントリポイントが存在するかどうかで判断しています。
DPIはGetDpiForMonitor API(Windows 8.1以降)が使える場合はこの関数でウィンドウが属するモニターのDPIを、使えない場合GetDeviceCaps(hdc,LOGPIXELSX)によりディスクトップのDPIを取得します。
MessageBoxをサブクラス化する
メッセージボックスの場合、ダイアログボックスのプロシージャーはシステム内にありますのでフック関数でメッセージボックスが開いたときに制御をMsgBoxHookProcに移すようにSetWindowsHookEx関数で設定します。MsgBoxHookProc
メッセージボックスの初期化時に呼び出されメッセージボックスであることを確認し、メッセージボックスの場合、サブクラス化します。サブクラス化によりメッセージボックスのウィンドウプロシージャがメッセージを処理する前にMsgBoxSubclassProcへ先に制御がわたるようにSetWindowSubclass関数を実行します。
MsgBoxSubclassProc
WM_INITDIALOG
現DPIの取得を行い、ダイアログボックスの座標単位とスクリーン座標の変換に必要な定数をGetActualDialogBaseUnitsで取得します。MsgBoxscaleChange関数でダイアログボックス内の各コントロールの大きさ等を変更します。WM_DPICHANGED
引数から現DPIを取得しMsgBoxscaleChange関数でダイアログボックス内の各コントロールの大きさ等を変更します。WM_NCCREATE
Wndows10(Version1607)の時だけ非クライアント領域の処理するためEnableNonClientDpiScaling関数を呼び出します。MsgBoxscaleChange
現在のフォントを取得しスケールに応じたフォントを新規に作成し設定します。スケールからダイアログボックスの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
各コントロールウィンドウをEnumChildWindowsで列挙します。
EnumWndProc
EnumChildWindowsから呼び出されるEnumWndProc関数によりスケールに応じた位置、サイズ、文字サイズを設定します。スケールからコントロールの大きさを計算し、AdjustWindowRectによりクライアントサイズから必要なウィンドウサイズを計算しSetWindowPosによりダイアログボックスの大きさを変更します。
コントロールのフォントをWM_SETTEXTメッセージにより変更します。
GetActualDialogBaseUnits
ダイアログボックス座標の計算に必要な単位を取得します。YogaPro3では横が13、縦が31ピクセルが取得されました。
win10ver2
Windows 10のバージョンを取得する関数です。詳細は、Windows 10のバージョン番号をRtlGetVersionのビルド番号から取得するを参照してください。
MessageBox API
MessgeBox APIのプロトタイプは以下の通りです。int MessageBox(HWND hWnd,TCHAR* lpText,TCHAR* lpCaption,UINT uType);
hWnd
親ウィンドウのハンドルを指定します。親ウィンドウがない場合はNULLを指定します。lpText
メッセージボックス内の文字列を指定します。lpCaption
メッセージボックスのタイトルを指定します。NULLを指定するとタイトルはエラーと表示されます。uType
メッセージボックスの動作を指定します。複数の動作はor演算子|で合成します。ボタンの表示の制御
ボタン | フラグ |
---|---|
MB_ABORTRETRYIGNORE | |
MB_CANCELTRYCONTINUE | |
MB_OK | MB_HELP | |
MB_OK | |
MB_OKCANCEL | |
MB_RETRYCANCEL | |
MB_YESNO | |
MB_YESNOCANCEL |
アイコンの表示
アイコン | フラグ |
---|---|
MB_ICONEXCLAMATION | |
MB_ICONINFORMATION | |
MB_ICONQUESTION | |
MB_ICONSTOP |
ディフォルトボタン
左から何個目のボタンをディフォルトボタンとするかを指定できます。ディフォルトボタン | |
---|---|
1個目のボタン | MB_DEFBUTTON1 |
2個目のボタン | MB_DEFBUTTON2 |
3個目のボタン | MB_DEFBUTTON3 |
4個目のボタン | MB_DEFBUTTON4 |
戻り値
意味 | フラグ |
---|---|
[中止]ボタンが押された | IDABORT |
[キャンセル]ボタンが押された | IDCANCEL |
[続行]ボタンが押された | IDCONTINUE |
[無視]ボタンが押された | IDIGNORE |
[いいえ]ボタンが押された | IDNO |
[OK]ボタンが押された | IDOK |
[再試行]ボタンが押された | IDRETRY |
[再実行]ボタンが押された | IDTRYAGAIN |
[はい]ボタンが押された | IDYES |
プログラムソース
// メッセージボックスサンプル(フック・サブクラス化・高DPI)
// Visual C++ 2013 32/64bit
// Windows 10のバージョン設定(Windows10以前は0を指定)
#define WIN10VERDEF 0 //1703以降の場合Windows自身が自動的に対応
#include <windows.h>
#include <CommCtrl.h>
#include <tchar.h>
#if WIN10VERDEF < 1703
#pragma comment (lib, "comctl32.lib")
#if 1500 < _MSC_VER
#include <shellscalingapi.h>
# pragma comment(lib, "Shcore.lib")
#else
#define WM_DPICHANGED 0x02E0
typedef enum MONITOR_DPI_TYPE {
MDT_EFFECTIVE_DPI = 0,
MDT_ANGULAR_DPI = 1,
MDT_RAW_DPI = 2,
MDT_DEFAULT = MDT_EFFECTIVE_DPI
} MONITOR_DPI_TYPE;
#endif
#ifndef _DPI_AWARENESS_CONTEXTS_
DECLARE_HANDLE(DPI_AWARENESS_CONTEXT);
#endif
typedef HRESULT(_stdcall *GetDpiForMonitorFunc)(HMONITOR, MONITOR_DPI_TYPE, UINT *, UINT *);
typedef HRESULT(_stdcall* EnableNonClientDpiScalingFunc)(HWND);
typedef HRESULT(_stdcall* SetThreadDpiAwarenessContextFunc)(DPI_AWARENESS_CONTEXT);
typedef BOOL(_stdcall *SetProcessDPIAwareFunc)(VOID);
static HMODULE hModUser32;
static EnableNonClientDpiScalingFunc NCCfunc = 0;
static SetThreadDpiAwarenessContextFunc ThreadAwareFunc = 0;
#ifndef _DPI_AWARENESS_CONTEXTS_
typedef enum _DPI_AWARENESS {
DPI_AWARENESS_INVALID = -1,
DPI_AWARENESS_UNAWARE = 0,
DPI_AWARENESS_SYSTEM_AWARE = 1,
DPI_AWARENESS_PER_MONITOR_AWARE = 2,
} DPI_AWARENESS;
#define DPI_AWARENESS_CONTEXT_UNAWARE ((DPI_AWARENESS_CONTEXT)-1)
#define DPI_AWARENESS_CONTEXT_SYSTEM_AWARE ((DPI_AWARENESS_CONTEXT)-2)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE ((DPI_AWARENESS_CONTEXT)-3)
#define DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 ((DPI_AWARENESS_CONTEXT)-4)
#define DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED ((DPI_AWARENESS_CONTEXT)-5)
#endif
HHOOK MyHookHandle;
HWND hDlgMessageBox = 0;
static HFONT hFont = 0;
static int nowDPIx = 96;
static int nowDPIy = 96;
static UINT nowScale = 100;
static UINT initScale = 100;
static int height;
static HMODULE hMod;
static GetDpiForMonitorFunc func = 0;
static SIZE baseUnit;
struct DLG_CHILD_SCALE{
HWND hDlg;
SIZE unit;
UINT oldScale;
UINT newScale;
HFONT hFont;
};
// フック関数
LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam);
// メッセージボックスのサブクラス化
LRESULT CALLBACK MsgBoxSubclassProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
// DPIスケールの変更
HFONT MsgBoxscaleChange(HWND hDlg, int nowScale, int oldScale, SIZE& baseUnit, HFONT hFont);
// メッセージボックス内の各ウィンドウ(コントロールの移動及び大きさの変更)
BOOL CALLBACK EnumWndProc(HWND hWnd, LPARAM lParam);
BOOL GetActualDialogBaseUnits(HWND hDlg, SIZE *baseUnit);
int win10ver2(void);
int WIN10VER;
#endif // WIN10VERDEF < 1703
int WINAPI _tWinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, TCHAR* lpsCmdLine, int nCmdShow){
#if WIN10VERDEF < 1703
WIN10VER = win10ver2();
hModUser32 = LoadLibrary(L"User32.dll");
NCCfunc = (EnableNonClientDpiScalingFunc)GetProcAddress(hModUser32, "EnableNonClientDpiScaling");
if (WIN10VER == 1607) // AnniversaryUpdate
NCCfunc = (EnableNonClientDpiScalingFunc)GetProcAddress(hModUser32, "EnableNonClientDpiScaling");
else
NCCfunc = 0;
ThreadAwareFunc = (SetThreadDpiAwarenessContextFunc)GetProcAddress(hModUser32, "SetThreadDpiAwarenessContext");
if (ThreadAwareFunc){
if (WIN10VER<1703)
(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
if (1703 <= WIN10VER && WIN10VER<1809)
(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (1809 <= WIN10VER)
(*ThreadAwareFunc)(DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED);
}
else{ // Windows Vista以降
SetProcessDPIAwareFunc ProcessAwareFunc = (SetProcessDPIAwareFunc)GetProcAddress(hModUser32, "SetProcessDPIAware");
if (ProcessAwareFunc)
(*ProcessAwareFunc)();
}
if (WIN10VER<1703) // Windows 10 Version 1703より古い場合はフック関数を使用する。
MyHookHandle = SetWindowsHookEx(WH_CALLWNDPROC, MsgBoxHookProc, NULL, GetCurrentThreadId());
#endif //WIN10VERDEF < 1703
#if WIN10VERDEF < 1703
if (WIN10VER<1703) // Windows 10 Version 1703より古い場合はフック関数を使用する。
MyHookHandle = SetWindowsHookEx(WH_CALLWNDPROC, MsgBoxHookProc, NULL, GetCurrentThreadId());
#endif //WIN10VERDEF < 1703
if (MessageBox(0, TEXT("Hello! 漢字"), TEXT("Message"), MB_YESNO | MB_ICONEXCLAMATION) == IDYES){
MessageBox(0, _TEXT("はいが押されました"), _TEXT("戻り値"), MB_OK);
}
#if WIN10VERDEF < 1703
if (hModUser32)
FreeLibrary(hModUser32);
#endif // WIN10VERDEF < 1703
return 0;
}
#if WIN10VERDEF < 1703
// フック関数
LRESULT CALLBACK MsgBoxHookProc(int nCode, WPARAM wParam, LPARAM lParam){
// コード判断
if (nCode >= 0){
if (nCode == HC_ACTION){ // システムがウィンドウをアクティブ化
CWPSTRUCT* pcwp = (CWPSTRUCT*)lParam;
HWND hDlg = pcwp->hwnd;
hDlgMessageBox = hDlg;
if (pcwp->message == WM_INITDIALOG){
TCHAR name[16];
GetClassName(hDlg, name, sizeof(name) / sizeof(name[0]));
if (_tcscmp(name, _TEXT("#32770")) == 0)// MessageBoxのクラス名
SetWindowSubclass(hDlg, MsgBoxSubclassProc, 1, 0);
// フック解除
UnhookWindowsHookEx(MyHookHandle);
}
}
}
return CallNextHookEx(MyHookHandle, nCode, wParam, lParam);
}
// メッセージボックスのサブクラス化
LRESULT CALLBACK MsgBoxSubclassProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData){
if (uMsg == UINT(WM_INITDIALOG)){
HMONITOR hMonitor = MonitorFromWindow(hDlg, MONITOR_DEFAULTTONULL);
hMod = LoadLibrary(L"Shcore.dll");
func = (GetDpiForMonitorFunc)GetProcAddress(hMod, "GetDpiForMonitor");
UINT hDPI, vDPI;
if (func)
(*func)(hMonitor, MDT_EFFECTIVE_DPI, &hDPI, &vDPI);
else{
HDC hdc = GetDC(NULL); // ディスクトップ
hDPI = GetDeviceCaps(hdc, LOGPIXELSX);
vDPI = GetDeviceCaps(hdc, LOGPIXELSY);
}
nowDPIx = hDPI;
nowDPIy = vDPI;
nowScale = MulDiv(nowDPIx, 100, 96);
GetActualDialogBaseUnits(hDlg, &baseUnit);
UINT oldScale = nowScale;
hFont = MsgBoxscaleChange(hDlg, nowScale, oldScale, baseUnit, hFont);
return DefSubclassProc(hDlg, uMsg, wParam, lParam);
}
if (uMsg == UINT(WM_DPICHANGED)){
UINT oldScale = nowScale;
nowDPIx = LOWORD(wParam);
nowDPIy = HIWORD(wParam);
nowScale = MulDiv(nowDPIx, 100, 96);
hFont = MsgBoxscaleChange(hDlg, nowScale, oldScale, baseUnit, hFont);
return 0;
}
if (uMsg == UINT(WM_NCCREATE)) { // Windows 10 Version 1607のみ呼び出す
if (NCCfunc)
(*NCCfunc)(hDlg);
return DefWindowProc(hDlg, uMsg, wParam, lParam);
}
switch (uMsg) {
case (UINT)WM_DESTROY:
RemoveWindowSubclass(hDlg, MsgBoxSubclassProc, 1);
break;
default:
break;
}
return DefSubclassProc(hDlg, uMsg, wParam, lParam);
}
// DPIスケールの変更
HFONT MsgBoxscaleChange(HWND hDlg, int nowScale, int oldScale, SIZE& baseUnit, HFONT hFont){
LOGFONT lf;
HWND hWnd = GetWindow(hDlg, GW_CHILD);
HDC hdc = GetDC(hWnd);
HFONT hfont9 = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0);
GetObject(hfont9, sizeof(LOGFONT), &lf);
int height;
height = lf.lfHeight * 100 / oldScale;
lf.lfHeight = (height)*nowScale;
lf.lfHeight /= 100;
HFONT hFont2 = CreateFontIndirect(&lf);
LOGFONT lfont;
HFONT hFontOld = (HFONT)SelectObject(hdc, hFont2);
TEXTMETRIC metrics;
GetTextMetrics(hdc, &metrics);
SelectObject(hdc, hFontOld);
HFONT htemp = (HFONT)SendMessage(hDlg, WM_GETFONT, 0, 0);
GetObject(htemp, sizeof(LOGFONT), &lfont);
ReleaseDC(hDlg, hdc);
RECT rc;
GetClientRect(hDlg, &rc);
int w = rc.right - rc.left;
int h = rc.bottom - rc.top;
w = MulDiv(w, nowScale, oldScale);
h = MulDiv(h, nowScale, oldScale);
rc.left = 0;
rc.top = 0;
rc.right = w;
rc.bottom = h;
LONG style = GetWindowLong(hDlg, GWL_STYLE);
AdjustWindowRectEx(&rc, style, FALSE, 0);
SetWindowPos(hDlg, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER);
DLG_CHILD_SCALE d;
d.hDlg = hDlg;
d.hFont = hFont2;
d.newScale = nowScale;
d.oldScale = oldScale;
d.unit = baseUnit;
EnumChildWindows(hDlg, EnumWndProc, (LPARAM)&d);
InvalidateRect(hDlg, NULL, FALSE);
UpdateWindow(hDlg);
if (hFont)
DeleteObject(hFont);
return hFont = hFont2;
}
// メッセージボックス内の各ウィンドウ(コントロールの移動及び大きさの変更)
BOOL CALLBACK EnumWndProc(HWND hWnd, LPARAM lParam){
POINT pt;
DLG_CHILD_SCALE* d = (DLG_CHILD_SCALE*)lParam;
pt.x = 0;
pt.y = 0;
ClientToScreen(hWnd, &pt);
ScreenToClient(d->hDlg, &pt);
RECT rc;
GetClientRect(hWnd, &rc);
int cx, cy, w, h;
cx = MulDiv(pt.x, 4, d->unit.cx);
cy = MulDiv(pt.y, 8, d->unit.cy);
cx = MulDiv(cx, d->newScale, d->oldScale);
cy = MulDiv(cy, d->newScale, d->oldScale);
cx = MulDiv(cx, d->unit.cx, 4);
cy = MulDiv(cy, d->unit.cy, 8);
w = MulDiv(rc.right, d->newScale, d->oldScale);
h = MulDiv(rc.bottom, d->newScale, d->oldScale);
rc.left = cx;
rc.top = cy;
rc.right = cx + w;
rc.bottom = cy + h;
LONG style = GetWindowLong(hWnd, GWL_STYLE);
LONG exStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
AdjustWindowRectEx(&rc, style, FALSE, exStyle);
SetWindowPos(hWnd, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER);
SendMessage(hWnd, WM_SETFONT, (WPARAM)d->hFont, MAKELPARAM(TRUE, 0));
UpdateWindow(hWnd);
return TRUE;
}
BOOL GetActualDialogBaseUnits(HWND hDlg, SIZE *baseUnit){
RECT rect;
BOOL result;
rect.left = 4;
rect.top = 8;
if (result = MapDialogRect(hDlg, &rect)) {
baseUnit->cx = rect.left;
baseUnit->cy = rect.top;
}
return result;
}
// DLL内の関数へのポインタ型を定義
typedef void (WINAPI *RtlGetVersion_FUNC)(OSVERSIONINFOEXW*);
int win10ver2(void){
int ver = 0;
HMODULE hMod;
OSVERSIONINFOEXW osw;
hMod = LoadLibrary(TEXT("ntdll.dll"));
RtlGetVersion_FUNC func;
if (hMod){
func = (RtlGetVersion_FUNC)GetProcAddress(hMod, "RtlGetVersion");
if (func == 0){
FreeLibrary(hMod);
return FALSE;
}
ZeroMemory(&osw, sizeof(osw));
osw.dwOSVersionInfoSize = sizeof(osw);
func(&osw);
FreeLibrary(hMod);
if (osw.dwMajorVersion == 10){
switch (osw.dwBuildNumber){
case 10240: ver = 1507; break;
case 10586: ver = 1511; break;
case 14393: ver = 1607; break;
case 15063: ver = 1703; break;
case 16299: ver = 1709; break;
case 17134: ver = 1803; break;
case 17763: ver = 1809; break;
default: ver = 1809; break;
}
return ver;
}
}
return -1;
}
#endif // WIN10VERDEF < 1703
ソースファイルと実行ファイルのダウンロード
Copyright (C) 2012 山本ワールド All Rights Reserved.