How Can I Check if a Window is an MDI Window? - c#

I imagine there's some user32.dll call that I can use to verify if a window is an MDI window, like using DefMDIChildProc and seeing if it fails, but I wonder if there's any limitations to this, or if there's a better way to do this? Is checking for a Parent sufficient?
For simplicity's sake, what I'm ultimately hoping for is an IsMDI(IntPtr ptr) kind of call...
Thoughts? Suggestions?

I've figured it out (with the help of pinvoke.net) - you can find out based on the Extended Windows Styles:
public static bool IsMDI(IntPtr hwnd)
{
WINDOWINFO info = new WINDOWINFO();
info.cbSize = (uint)Marshal.SizeOf(info);
GetWindowInfo(hwnd, ref info);
//0x00000040L is the style for WS_EX_MDICHILD
return (info.dwExStyle & 0x00000040L)==1;
}
[StructLayout(LayoutKind.Sequential)]
private struct WINDOWINFO
{
public uint cbSize;
public RECT rcWindow;
public RECT rcClient;
public uint dwStyle;
public uint dwExStyle;
public uint dwWindowStatus;
public uint cxWindowBorders;
public uint cyWindowBorders;
public ushort atomWindowType;
public ushort wCreatorVersion;
public WINDOWINFO(Boolean? filler)
: this() // Allows automatic initialization of "cbSize" with "new WINDOWINFO(null/true/false)".
{
cbSize = (UInt32)(Marshal.SizeOf(typeof(WINDOWINFO)));
}
}
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowInfo(IntPtr hwnd, ref WINDOWINFO pwi);

If the controls are in your own .NET application, the Form class has properties for working with MDI windows:
Form.IsMdiChild
Form.IsMdiContainer
Form.MdiParent
Form.MdiChildren

Related

Global low-level mouse hook doesn't get called when UWP desktop app is out of focus

I'm creating a desktop UWP app and I need to set a global low level mouse hook to detect and change the position of it when it is moved to certain locations of the screen.
It works fine while my app's window is in focus and the values get logged correctly to the output window(meaning that the hook is working correctly).
This UWP app isn't going to be on the store and will only be used on a Windows 10 desktop (1903+).
I've tried calling SetWindowsHookEx on a different thread (which didn't do anything).
Tried also passing a thread ID when calling SetWindowsHookEx to no avail.
Also tried using the following restricted capabilities to prevent the app to suspend when not on focus: extendedExecutionUnconstrained and extendedBackgroundTaskTime along with the PreventFromSuspending method shown here.
Another thing I tried was to set uiAccess to true on the app manifest, which also didn't work.
The global hook is supposed to work even when the app isn't in the foreground, but instead it just works when it has the active window focus.
#region Structures
[StructLayout(LayoutKind.Sequential)]
/* MSLLHOOKSTRUCT */
public struct NativeMouseLowLevelHook
{
public override string ToString()
{
return $"{nameof(Point)}: {Point}, {nameof(MouseData)}: {MouseData}, {nameof(Flags)}: {Flags}";
}
public NativePoint Point;
public int MouseData;
public int Flags;
public int Time;
public UIntPtr ExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
public class NativePoint
{
public override string ToString()
{
return $"{nameof(X)}: {X}, {nameof(Y)}: {Y}";
}
public int X;
public int Y;
}
#endregion
#region Hook
public class ManagedMouseHook : SafeHandleZeroOrMinusOneIsInvalid
{
public static List<ManagedMouseHook> KnownHooks { get; set; } = new List<ManagedMouseHook>();
public HookProc HookImpl { get; set; }
public ManagedMouseHook() : base(true)
{
Hook();
}
private void Hook()
{
HookImpl = NativeHookCallback;
KnownHooks.Add(this);
using (var curProcess = Process.GetCurrentProcess())
using (var curModule = curProcess.MainModule)
{
DoHook(curModule);
}
}
private void DoHook(ProcessModule curModule)
{
SetHandle(SetWindowsHookEx(14 /*WH_MOUSE_LL*/, HookImpl, GetModuleHandle(curModule.ModuleName), 0));
}
private bool UnHook()
{
var result = UnhookWindowsHookEx(DangerousGetHandle());
KnownHooks.Remove(this);
HookImpl = null;
return result;
}
/* LowLevelMouseProc */
private IntPtr NativeHookCallback(int code, IntPtr wparam, IntPtr lparam)
{
if (code >= 0)
{
var info = (NativeMouseLowLevelHook) Marshal.PtrToStructure(lparam,
typeof(NativeMouseLowLevelHook));
Debug.WriteLine(info); //Output example: Point: X: 408, Y: 535, MouseData: 0, Flags: 0
return new IntPtr(-1);
}
return CallNextHookEx(IntPtr.Zero, code, wparam, lparam);
}
protected override bool ReleaseHandle()
{
return UnHook();
}
}
#endregion
#region Interop
public delegate IntPtr HookProc(int code, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int hookType, HookProc lpfn, IntPtr hMod, ulong dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
IntPtr lParam);
#endregion
Then, to start using it:
public ManagedMouseHook MouseHook { get; set; }
MouseHook/*property*/ = new ManagedMouseHook();
And to unhook:
MouseHook/*property*/.Close();
UWP apps are running in the sandbox, so that doesn't sound strange. If you think a bit it is a security problem if the app can receive such an input and as 'Security' is listed as No.1 characteristic of the UWP this behavior is expected.
I've decided with the help of the comments that the best way to do this is to use my UWP app as a front-end and using a win32 app for the hooking functionality itself.

Disable animation effects for windows with C#

I'm trying to disable the "fading" animation in windows which happens whenever you open or maximize/minimize a window.
Of course it can be done manually by unticking the checkbox of animate windows when minimizing and maximizing
I'm trying to do this through the SystemParametersInfo
This is my call:
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool SystemParametersInfo(uint uiAction, uint uiParam, bool pvParam,uint fWinIni);
private static UInt32 SPIF_SENDCHANGE = 0x02;
private static UInt32 SPI_SETUIEFFECTS = 0x103F;
public static void Main()
{
bool res= SystemParametersInfo(SPI_SETUIEFFECTS, 0, false, SPIF_SENDCHANGE);
}
result value is always True , so I know the function was successfully called.
But I can't see any results...Windows still keep animating any window I resize.
I compile this as AnyCPU, running as adminstrator on Windows 10.
for #cody gray this is the code (added the ref keyword to the ai paramater and converted the Marshal.Sizeof(ai) to `uint).
[StructLayout(LayoutKind.Sequential)]
public struct ANIMATIONINFO
{
public uint cbSize;
public int iMinAnimate;
};
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SystemParametersInfo(uint uiAction,
uint uiParam,
ref ANIMATIONINFO pvParam,
uint fWinIni);
public static uint SPIF_SENDCHANGE = 0x02;
public static uint SPI_SETANIMATION = 0x0049;
public static void Main()
{
ANIMATIONINFO ai=new ANIMATIONINFO();
ai.cbSize = (uint)Marshal.SizeOf(ai);
ai.iMinAnimate = 0; // turn all animation off
SystemParametersInfo(SPI_SETANIMATION, 0, ref ai, SPIF_SENDCHANGE);
}
One last question-if i would like to get back to the original state-which means i want to activate the aniamtions again,what parameter should be changed in order to do this?
You are not setting the right option when you call SystemParametersInfo. The one that controls the minimize/maximize animation effect (labeled in the UI as "Animate windows when minimizing and maximizing") is SPI_SETANIMATION.
Using it is a bit more complicated, because the pvParam parameter must point to an ANIMATIONINFO structure. It is rather pointless, because the struct only has one meaningful member, but that's the way the API was designed. Presumably, the intent years ago was for this to be a toggle for all of the shell animation effects, but for whatever reason, that didn't end up happening, and separate SPI_* values were used for each of them. You unfortunately picked the wrong one. It is a long list.
Sample code in C#:
[StructLayout(LayoutKind.Sequential)]
public struct ANIMATIONINFO
{
public uint cbSize;
public int iMinAnimate;
};
[DllImport("user32", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SystemParametersInfo(uint uiAction,
uint uiParam,
ref ANIMATIONINFO pvParam,
uint fWinIni);
public static uint SPIF_SENDCHANGE = 0x02;
public static uint SPI_SETANIMATION = 0x0049;
public static void Main()
{
ANIMATIONINFO ai;
ai.cbSize = Marshal.SizeOf(ai);
ai.iMinAnimate = 0; // turn all animation off
SystemParametersInfo(SPI_SETANIMATION, 0, ai, SPIF_SENDCHANGE);
}
Note that this is a global setting, affecting all applications. It is exceedingly rare that an application would ever need to toggle this switch, unless you were making a desktop customization utility. As such, you will require administrative privileges to change this setting.
There is also the DWMWA_TRANSITIONS_FORCEDISABLED flag that you can use with the DwmSetWindowAttribute function to disable transitions when a window is hidden or shown.
The advantage of this is that it is a local solution—you can change the setting only for a single window. But it won't do anything if you are not using DWM (on older versions of Windows), and the minimize/maximize transitions may still be visible. I haven't comprehensively tested the interaction between these two options.
Hans Passant linked you to one of his answers that contains an embedded example of how to call DwmSetWindowAttribute from C#. Here are the relevant bits:
const int DWMWA_TRANSITIONS_FORCEDISABLED = 3;
[DllImport("dwmapi", PreserveSig = true))]
static extern int DwmSetWindowAttribute(IntPtr hWnd, int attr, ref int value, int attrLen);
// in the form's constructor:
// (Note: in addition to checking the OS version for DWM support, you should also check
// that DWM composition is enabled---or at least gracefully handle the function's
// failure when it is not. Instead of S_OK, it will return DWM_E_COMPOSITIONDISABLED.)
if (Environment.OSVersion.Version.Major >= 6)
{
int value = 1; // TRUE to disable
DwmSetWindowAttribute(this.Handle,
DWMWA_TRANSITIONS_FORCEDISABLED,
ref value,
Marshal.SizeOf(value));
}

C# method's type signature is not pinvoke compatible

I want to use API from Omron V4KU, the documentation described like this :
Original c# code :
const string DllLocation = #"..\..\Libs\Omron\OMCR.dll";
[DllImport(DllLocation)]
public static extern LPCOMCR OMCR_OpenDevice(string lpcszDevice, LPCOMCR_OPTION lpcOption);
public void Start()
{
var lpcOption = new LPCOMCR_OPTION();
var result = OMCR_OpenDevice(null, lpcOption); // error method's type signature is not pinvoke compatible
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR
{
public string lpcszDevice;
public IntPtr hDevice;
public uint lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct LPCOMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
if I missed or wrong in writing code?
sorry, my english is bad. thanks for help.
Start by defining the union structure correctly:
// OMCR_OPTION.COM
[StructLayout(LayoutKind.Sequential)]
public struct OmcrCom
{
public IntPtr Reserved0;
public uint BaudRate;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
public IntPtr Reserved1;
public IntPtr Reserved2;
}
// OMCR_OPTION.USB
[StructLayout(LayoutKind.Sequential)]
public struct OmcrUsb
{
public uint Reserved0;
public uint Reserved1;
public uint Reserved2;
public uint Reserved3;
}
// OMCR_OPTION (union of COM and USB)
[StructLayout(LayoutKind.Explicit)]
public struct OmcrOptions
{
[FieldOffset(0)]
public OmcrCom Com;
[FieldOffset(0)]
public OmcrUsb Usb;
}
// OMCR
[StructLayout(LayoutKind.Sequential)]
public struct OmcrDevice
{
public string Device;
public IntPtr DeviceHandle;
public IntPtr DevicePointer;
}
[DllImport(dllName: DllLocation, EntryPoint = "OMCR_OpenDevice"]
public static extern IntPtr OmcrOpenDevice(string type, ref OmcrOptions options);
And then call the method, something like:
var options = new OmcrOptions();
options.Com.BaudRate = 115200; // or whatever you need to set
var type = "COM"; // is this USB/COM? not sure
OmcrDevice device;
var devicePtr = OmcrOpenDevice(type, ref options);
if (devicePtr == IntPtr.Zero)
device = (OmcrDevice)Marshal.PtrToStructure(devicePtr, typeof(OmcrDevice));
Well, for one, the documentation is asking you to pass LPCOMCR_OPTION as a pointer - you're passing it as a value. Using ref should help. There's another problem, though, and that's the return value - again, you're trying to interpret it as a value, while the docs say it's a pointer. However, this is a lot trickier than the first error - as far as I'm aware, your only options are using a C++/CLI interop library, or expecting IntPtr as a return value. In any case, you need to handle proper deallocation of the memory you get this way.
You need to do it like this:
[StructLayout(LayoutKind.Sequential)]
public struct OMCR
{
[MarshalAs(UnmanagedType.LPStr)]
public string lpcszDevice;
public IntPtr hDevice;
public IntPtr lpcDevice;
}
[StructLayout(LayoutKind.Sequential)]
public struct OMCR_OPTION
{
public uint dwReserved0;
public uint dwReserved1;
public uint dwReserved2;
public uint dwReserved3;
}
[DllImport(DllLocation, CallingConvention = CallingConvention.???,
SetLastError = true)]
public static extern IntPtr OMCR_OpenDevice(string lpcszDevice,
ref OMCR_OPTION lpcOption);
You need to replace CallingConvention.??? with the appropriate calling convention. We cannot tell from the question what that is. You will have to find out by reading the header file.
The return value is a pointer to OMCR. You need to hold on to this pointer and pass it to OMCR_CloseDevice when you are finished with it.
In order to obtain an OMCR value you would do the following:
OMCR_OPTION Option = new OMCR_OPTION(); // not sure how to initialize this
IntPtr DevicePtr = OMCR_OpenDevice(DeviceType, ref Option);
if (DevicePtr == IntPtr.Zero)
throw new Win32Exception();
OMCR Device = (OMCR)Marshal.PtrToStructure(DevicePtr, typeof(OMCR));

Using ShellExecuteEx to open file properties sheet under explorer process, not calling process

My issue is with a PropertySheetExtension, but the same behavior seems present with the default file properties sheet. The issue with the following code:
// Snippet from http://stackoverflow.com/a/1936957/124721
using System.Runtime.InteropServices;
[DllImport("shell32.dll", CharSet = CharSet.Auto)]
static extern bool ShellExecuteEx(ref SHELLEXECUTEINFO lpExecInfo);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct SHELLEXECUTEINFO
{
public int cbSize;
public uint fMask;
public IntPtr hwnd;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpVerb;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpFile;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpParameters;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpDirectory;
public int nShow;
public IntPtr hInstApp;
public IntPtr lpIDList;
[MarshalAs(UnmanagedType.LPTStr)]
public string lpClass;
public IntPtr hkeyClass;
public uint dwHotKey;
public IntPtr hIcon;
public IntPtr hProcess;
}
private const int SW_SHOW = 5;
private const uint SEE_MASK_INVOKEIDLIST = 12;
public static bool ShowFileProperties(string Filename)
{
SHELLEXECUTEINFO info = new SHELLEXECUTEINFO();
info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
info.lpVerb = "properties";
info.lpFile = Filename;
info.nShow = SW_SHOW;
info.fMask = SEE_MASK_INVOKEIDLIST;
return ShellExecuteEx(ref info);
}
Is that it opens the file properties sheet under the calling applications process, instead of explorer.exe. Due to this the property sheet closes when the application is closed, which is not the behavior I need. I need the sheet to remain open when the application exits. Another issue is if the application opens the property sheet, and then you right click on the file through explorer and click properties it will open a second, duplicate sheet.
I have tried using ShellExecuteEx and setting the parent window handle with explorer's main window, or from GetShellWindow, but that didn't help.
Is there another way to get this property sheet to open under the explorer.exe process?
Your process is actually supposed to stick around until all shell threads have ended.
To do this you must implement a free threaded interface that supports IUnknown and tell Windows about it by calling SHSetInstanceExplorer.
More information and a C++ example implementation can be found in this blog post.

C# Graphics Programming on OS [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
This is an odd question. I am interested in building an application for navigation on the computer. I want to give the user "physical cues" when they accomplish something. For instance, a white halo expanding when a folder is clicked. Can this be done with C#. One user suggested I expand this with more detail to be more specific. I want to use the Kinect to allow the user to navigate through the OS. However, I want them to be able to use both hands so I don't want to just attach the hand to the mouse pointer. I would like to give the user some visual feedback on their interaction with the OS. I am having trouble thinking of the best way to do this. So I want to create visual effects on the OS, but not in any specific window like a game window.
Most of the graphic tutorials I see either involve building a window and rendering pipeline for that window; or using WPF and Silverlight graphics and animations. I need more flexibility then that because this will be for the OS and not for a particular application. I'm having trouble figuring out where to start and if it is even possible using .NET or the Mono Framework.
Am I better off just using C++ to accomplish this goal. Please let me know if this is too open ended of a question. I am trying to find how to start going about something like this. Thanks!
.NET WinForms, like C++ WinForms use GDI+, .NET is just more abstracted. You still have access to native code via p/invoke and the ability to override protected members in the abstracted BCL meaning you still have a fair level of control. So unless you're talking about a specific graphics library I don't think .NET WinForms are lesser than C++. in this context.
Regarding your task I would research layered windows. Sorry, I don't have any comprehensive references handy as I strugled to find them when I was learning but here's a class I mustered together that could help you get started with drawing on a Layered Window. Rather than deriving your Main form from Form derive from SingleLayeredForm:
public class SingleLayeredForm : Form
{
public new event PaintEventHandler Paint;
public SingleLayeredForm()
{
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.StartPosition = FormStartPosition.Manual;
}
protected override CreateParams CreateParams
{
get
{
if (DesignMode) return base.CreateParams;
CreateParams createParams = base.CreateParams;
createParams.ExStyle = createParams.ExStyle | 0x80000;
return createParams;
}
}
public void SetBitmap(Bitmap bitmap)
{
if (!IsHandleCreated || DesignMode) return;
IntPtr oldBits = IntPtr.Zero;
IntPtr screenDC = WinAPI.GetDC(IntPtr.Zero);
IntPtr hBitmap = IntPtr.Zero;
IntPtr memDC = WinAPI.CreateCompatibleDC(screenDC);
try
{
Point topLocation = new Point(this.Left, this.Top);
Size bitmapSize = new Size(bitmap.Width, bitmap.Height);
WinAPI.BLENDFUNCTION blendFunc = new WinAPI.BLENDFUNCTION();
Point sourceLocation = Point.Empty;
hBitmap = bitmap.GetHbitmap(Color.FromArgb(0));
oldBits = WinAPI.SelectObject(memDC, hBitmap);
blendFunc.BlendOp = WinAPI.AC_SRC_OVER;
blendFunc.SourceConstantAlpha = 255;
blendFunc.AlphaFormat = WinAPI.AC_SRC_ALPHA;
blendFunc.BlendFlags = 0;
WinAPI.UpdateLayeredWindow(Handle, screenDC, ref topLocation, ref bitmapSize, memDC, ref sourceLocation, 0, ref blendFunc, WinAPI.ULW_ALPHA);
}
finally
{
if (hBitmap != IntPtr.Zero)
{
WinAPI.SelectObject(memDC, oldBits);
WinAPI.DeleteObject(hBitmap);
}
WinAPI.ReleaseDC(IntPtr.Zero, screenDC);
WinAPI.DeleteDC(memDC);
}
}
public new void Invalidate()
{
using (Bitmap bitmap = new Bitmap(this.ClientSize.Width, this.ClientSize.Height))
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
graphics.SmoothingMode = SmoothingMode.HighSpeed;
graphics.CompositingMode = CompositingMode.SourceCopy;
if (this.Paint != null)
this.Paint(this, new PaintEventArgs(graphics, Rectangle.Empty));
}
SetBitmap(bitmap);
}
}
}
public sealed class WinAPI
{
[DllImport("user32.dll")]
public static extern bool HideCaret(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern short GetKeyState(int keyCode);
[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
[DllImport("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr hWnd, int id);
[DllImport("gdi32.dll", SetLastError = true)]
public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("user32.dll")]
public static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("user32.dll")]
public static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
public static extern bool DeleteDC(IntPtr hdc);
[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);
[DllImport("user32.dll", ExactSpelling = true, SetLastError = true)]
public static extern bool UpdateLayeredWindow(IntPtr hwnd, IntPtr hdcDst, ref Point pptDst, ref Size psize, IntPtr hdcSrc, ref Point pptSrc, uint crKey, [In] ref BLENDFUNCTION pblend, uint dwFlags);
[DllImport("user32.dll")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr ptr);
[DllImport("gdi32.dll")]
public static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
public static extern bool BitBlt(IntPtr hdcDest, int xDest, int yDest, int wDest, int hDest, IntPtr hdcSource, int xSrc, int ySrc, CopyPixelOperation rop);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT rect);
public const byte AC_SRC_OVER = 0;
public const byte AC_SRC_ALPHA = 1;
public const byte ULW_ALPHA = 2;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BLENDFUNCTION
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
}

Categories

Resources