I'm trying to create a message-only window to receive window messages from an MFC library class, within a winforms application.
I've tried subclassing NativeWindow, and in the constructor requesting a window handle like this:
CreateParams cp = new CreateParams();
cp.Parent = (IntPtr)HWND_MESSAGE;
this.CreateHandle(cp);
but I get a Win32Exception thrown with the message "Error creating window handle". How do I create a message-only window from windows forms? Is using NativeWindow the right approach?
Try that :
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
static IntPtr HWND_MESSAGE = new IntPtr(-3);
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
SetParent(this.Handle, HWND_MESSAGE);
}
I know this is 7.5 years old, but just in case anyone finds this, I thought I would respond. I used Microsoft's TimerNativeWindow code and removed the timer functionality. I ended up using this approach:
public class MyNativeWindow : NativeWindow
{
private readonly string _caption;
private const int WmClose = 0x0010;
[SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
private static readonly HandleRef HwndMessage = new HandleRef(null, new IntPtr(-3));
[DllImport("user32.dll", CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.None)]
private static extern IntPtr PostMessage(HandleRef hwnd, int msg, int wparam, int lparam);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.Process)]
private static extern int GetWindowThreadProcessId(HandleRef hWnd, out int lpdwProcessId);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
[ResourceExposure(ResourceScope.Process)]
private static extern int GetCurrentThreadId();
public MyNativeWindow(string caption)
{
_caption = caption;
}
public bool CreateWindow()
{
if (Handle == IntPtr.Zero)
{
CreateHandle(new CreateParams
{
Style = 0,
ExStyle = 0,
ClassStyle = 0,
Caption = _caption,
Parent = (IntPtr)HwndMessage
});
}
return Handle != IntPtr.Zero;
}
public void DestroyWindow()
{
DestroyWindow(true, IntPtr.Zero);
}
private bool GetInvokeRequired(IntPtr hWnd)
{
if (hWnd == IntPtr.Zero) return false;
int pid;
var hwndThread = GetWindowThreadProcessId(new HandleRef(this, hWnd), out pid);
var currentThread = GetCurrentThreadId();
return (hwndThread != currentThread);
}
private void DestroyWindow(bool destroyHwnd, IntPtr hWnd)
{
if (hWnd == IntPtr.Zero)
{
hWnd = Handle;
}
if (GetInvokeRequired(hWnd))
{
PostMessage(new HandleRef(this, hWnd), WmClose, 0, 0);
return;
}
lock (this)
{
if (destroyHwnd)
{
base.DestroyHandle();
}
}
}
public override void DestroyHandle()
{
DestroyWindow(false, IntPtr.Zero);
base.DestroyHandle();
}
}
I believe that you'll need to also specify a window class.
I fear that you must derive from a Form, and force the window invisible.
Another approach (in the case the class library is modifiable) is to run a message pump without a window (see Application.Run and Application.AddMessageFilter, or if you prefer pinvokes using PeekMessage & Co).
In this case you can send messages using PostThreadMessage by having the thread id which as run Application.Run, but actually you cannot synch with the application message pump thread because it doesn't wait message acknowledge.
Related
I'm creating an app that needs UI but not show up inside the taskbar, I know exactly how to do this in, winforms and wpf, but the window object doesn't contain a ShowInTaskBar variable that I can just change.
I have tried NotifyIcon but that doesn't hide it from the taskbar, I also can't find any documentation on if this is even possible.
I only create a stack overflow question when I've spent more than 5 hours on this, so any help would be appreciated.
Nick showed me this post https://stackoverflow.com/a/551847/14724147
Just add the window handle to the win32 api and it will hide it from alt+tab and the taskbar
public MainWindow()
{
InitializeComponent();
IntPtr hWnd = WindowNative.GetWindowHandle(this);
int exStyle = (int)GetWindowLong(hWnd, (int)GetWindowLongFields.GWL_EXSTYLE);
exStyle |= (int)ExtendedWindowStyles.WS_EX_TOOLWINDOW;
SetWindowLong(hWnd, (int)GetWindowLongFields.GWL_EXSTYLE, (IntPtr)exStyle);
}
#region Window styles
[Flags]
public enum ExtendedWindowStyles
{
// ...
WS_EX_TOOLWINDOW = 0x00000080,
// ...
}
public enum GetWindowLongFields
{
// ...
GWL_EXSTYLE = (-20),
// ...
}
[DllImport("user32.dll")]
public static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);
public static IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
int error = 0;
IntPtr result = IntPtr.Zero;
// Win32 SetWindowLong doesn't clear error on success
SetLastError(0);
if (IntPtr.Size == 4)
{
// use SetWindowLong
Int32 tempResult = IntSetWindowLong(hWnd, nIndex, IntPtrToInt32(dwNewLong));
error = Marshal.GetLastWin32Error();
result = new IntPtr(tempResult);
}
else
{
// use SetWindowLongPtr
result = IntSetWindowLongPtr(hWnd, nIndex, dwNewLong);
error = Marshal.GetLastWin32Error();
}
if ((result == IntPtr.Zero) && (error != 0))
{
throw new System.ComponentModel.Win32Exception(error);
}
return result;
}
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
private static extern IntPtr IntSetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLong", SetLastError = true)]
private static extern Int32 IntSetWindowLong(IntPtr hWnd, int nIndex, Int32 dwNewLong);
private static int IntPtrToInt32(IntPtr intPtr)
{
return unchecked((int)intPtr.ToInt64());
}
[DllImport("kernel32.dll", EntryPoint = "SetLastError")]
public static extern void SetLastError(int dwErrorCode);
#endregion
I'm using the following code trying to get OS wide keyboard inputs with no luck:
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
class InterceptKeys
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Main()
{
_hookID = SetHook(_proc);
while(true)
continue;
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
Console.WriteLine(vkCode);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
HookCallback is simply not being called. I have a suspicion it's trying to listen only to a form which doesn't exist rather than running system wide.
Low-level Windows hooks internally use Windows messaging. The thread that calls SetWindowsHookEx must have the message loop in the end, which allows to call HookCallback function. In C++ message loop looks like this:
MSG msg;
BOOL result;
for (;;)
{
result = GetMessage(&msg, nullptr, 0, 0);
if (result <= 0)
{
break;
}
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Find all required PInvoke definitions for GetMessage, TranslateMessage, DispatchMessage and MSG, translate this code to C# and place it instead of your endless loop while(true). You can find all this stuff at PInvoke.Net, see also this Microsoft forum discussion:
Console keyboard hook not getting called
https://social.msdn.microsoft.com/Forums/vstudio/en-US/ed5be22c-cef8-4615-a625-d05caf113afc/console-keyboard-hook-not-getting-called?forum=csharpgeneral
I'm obviously very late but just hoping I could help (if the OP haven't have yet the help he/she needed) so I'm posting my answer.
It says in the MSDN documentation that when you want to set a system-wide hook, you must give the hMod parameter a
handle to the DLL containing the hook procedure pointed to by the lpfn
parameter
and
If the dwThreadId parameter is zero or specifies the identifier of a
thread created by a different process, the lpfn parameter must point
to a hook procedure in a DLL
but, look at this:
SetWindowsHookEx(2, kbdHookProc, GetModuleHandle("user32"), 0)
kbdHookProc is a function in my C# winforms application but the value I gave in the hMod parameter is the hinstance obtained by loading user32.dll via GetModuleHandle. I am using the keyboard hook (WH_KEYBOARD) to monitor locking of capslock, numlock and scroll lock keys. Don't ask me why I did that and would it work or why it works because I don't know but, yes, IT WORKS!
For a full answer to this;
As Alex says, you'll need a message loop to process windows messages and call your hooks,
public class MessageLoop
{
[DllImport("user32.dll")]
private static extern int GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin,
uint wMsgFilterMax);
[DllImport("user32.dll")]
private static extern bool TranslateMessage([In] ref MSG lpMsg);
[DllImport("user32.dll")]
private static extern IntPtr DispatchMessage([In] ref MSG lpmsg);
[StructLayout(LayoutKind.Sequential)]
public struct MSG
{
IntPtr hwnd;
uint message;
UIntPtr wParam;
IntPtr lParam;
int time;
POINT pt;
int lPrivate;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
public static implicit operator System.Drawing.Point(POINT p)
{
return new System.Drawing.Point(p.X, p.Y);
}
public static implicit operator POINT(System.Drawing.Point p)
{
return new POINT(p.X, p.Y);
}
public override string ToString()
{
return $"X: {X}, Y: {Y}";
}
}
private Action InitialAction { get; }
private Thread? Thread { get; set; }
public bool IsRunning { get; private set; }
public MessageLoop(Action initialAction)
{
InitialAction = initialAction;
}
public void Start()
{
IsRunning = true;
Thread = new Thread(() =>
{
InitialAction.Invoke();
while (IsRunning)
{
var result = GetMessage(out var message, IntPtr.Zero, 0, 0);
if (result <= 0)
{
Stop();
continue;
}
TranslateMessage(ref message);
DispatchMessage(ref message);
}
});
Thread.Start();
}
public void Stop()
{
IsRunning = false;
}
}
I use a separate thread here to avoid blocking the main thread.
The InterceptKeys class as shown in the question needs no modification;
class InterceptKeys
{
// ...
public static void Main()
{
var loop = new MessageLoop(() => {
_hookID = SetHook(_proc);
});
while (Console.ReadKey(true) != ConsoleKey.X) // For exemplary purposes
{
continue;
}
loop.Stop();
}
// ...
}
I'd like to host a window of an external process inside my WPF application. I'm deriving HwndHost like this:
class HwndHostEx : HwndHost
{
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
private IntPtr ChildHandle = IntPtr.Zero;
public HwndHostEx(IntPtr handle)
{
this.ChildHandle = handle;
}
protected override System.Runtime.InteropServices.HandleRef BuildWindowCore(System.Runtime.InteropServices.HandleRef hwndParent)
{
HandleRef href = new HandleRef();
if (ChildHandle != IntPtr.Zero)
{
SetParent(this.ChildHandle, hwndParent.Handle);
href = new HandleRef(this, this.ChildHandle);
}
return href;
}
protected override void DestroyWindowCore(System.Runtime.InteropServices.HandleRef hwnd)
{
}
}
and using it like this:
HwndHostEx host = new HwndHostEx(handle);
this.PART_Host.Child = host;
where handle is a handle for an external window I'd like to host and PART_Host is a border inside my WPF window:
<StackPanel UseLayoutRounding="True"
SnapsToDevicePixels="True"
Background="Transparent">
<Border Name="PART_Host" />
...
This gives me an exception:
Hosted HWND must be a child window.
Sorry for my lack of knowledge but what is the proper way of hosting an external window inside WPF application?
Right before calling 'SetParent' do this:
public const int GWL_STYLE = (-16);
public const int WS_CHILD = 0x40000000;
SetWindowLong(this.ChildHandle, GWL_STYLE, WS_CHILD);
Looking at the docs:
You need a Win32 control compiled for CLI
You must create the control inside your WPF app.
It seems not possibile to "attach" an already running Window from another process inside a WPF app.
How do you get the handle you pass to your HwndHostEx constructor?
To add clarification, here's the final code for hosting a process in a WPF app, taken from Jose's answer:
class HwndHostEx : HwndHost
{
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, UInt32 dwNewLong);
private IntPtr ChildHandle = IntPtr.Zero;
public HwndHostEx(IntPtr handle)
{
this.ChildHandle = handle;
}
protected override System.Runtime.InteropServices.HandleRef BuildWindowCore(System.Runtime.InteropServices.HandleRef hwndParent)
{
HandleRef href = new HandleRef();
if (ChildHandle != IntPtr.Zero)
{
const int GWL_STYLE = (-16);
const int WS_CHILD = 0x40000000;
SetWindowLong(this.ChildHandle, GWL_STYLE, WS_CHILD);
SetParent(this.ChildHandle, hwndParent.Handle);
href = new HandleRef(this, this.ChildHandle);
}
return href;
}
protected override void DestroyWindowCore(System.Runtime.InteropServices.HandleRef hwnd)
{
}
}
// to create an instance:
var processName = "Whatever.exe";
var process = System.Diagnostics.Process.GetProcesses()
.FirstOrDefault(item => item.ProcessName.ToLowerInvariant() == processName && item.MainWindowHandle != IntPtr.Zero);
var handle = process?.MainWindowHandle;
if(handle != null)
{
var host = new HwndHostEx(handle.Value);
YourControl.Grid.Children.Add(host);
}
EDIT3:
I need a list from all windows a thread has. Not FindWindowsEnum() because it only returns Top level Windows.... I need the window handle befor it's visible
END EDIT3
EDIT2:
Short desciption: I need a method, where i can get a "window handle" from a thread.
END EDIT2:
EDIT:
First of all i can't use FindWindowsEnum()!!!
Because it only returns the top windows. I have to kill the window befor it is visible. So i have to get The Handle of the window by a Thread ID.
EDIT END:
I got a problem by a window which i have to close which is not focused or something like that. I have to close the window as it pops up. I only use code.
What i got:
This is the extern class where I import one dll and some functions which use the functions from the dll.
class FindWindowApi
{
//[DllImport("user32.dll", SetLastError = true)]
//public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public IntPtr[] GetWindowHandlesForThread(int threadHandle)
{
_results.Clear();
EnumWindows(WindowEnum, threadHandle);
return _results.ToArray();
}
public delegate int EnumWindowsProc(IntPtr hwnd, int lParam);
[DllImport("user32.Dll")]
public static extern int EnumWindows(EnumWindowsProc x, int y);
public List<IntPtr> _results = new List<IntPtr>();
public int WindowEnum(IntPtr hWnd, int lParam)
{
_results.Add(hWnd);
return 1;
}
[DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
public static extern IntPtr FindWindowByCaption(IntPtr ZeroOnly, string lpWindowName);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowText(
IntPtr handle,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder caption,
int count);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int GetWindowTextLength(IntPtr handle);
public const UInt32 WM_CLOSE = 0x0010;
}
at this class i use the functions from the extern class.
I start a Thread which runs every 10 milli secounds and kill a window in another method.
public Disk_UC()
{
InitializeComponent();
Thread killit = new Thread(this.killIt);
killit.Start();
comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
comboBox2.DropDownStyle = ComboBoxStyle.DropDownList;
Thread nt = new Thread(this.fillData);
nt.Start();
}
//Thread Method
private void killIt()
{
bool blac = false;
Int32 oldThreadID = threadID;
FindWindowApi api = new FindWindowApi();
while ((!blac)
&& (!MainDiag.isGoingDown))
{
if (oldThreadID != threadID)
{
IntPtr[] windows = api.GetWindowHandlesForThread(threadID);
if (windows != null && windows.Length > 0)
foreach (IntPtr hWnd in windows)
{
killWindow(hWnd, threadID);
}
}
Thread.Sleep(10);
}
}
//Method which calls all the extern dll stuff
private bool killWindow(IntPtr handle, int param)
{
var length = FindWindowApi.GetWindowTextLength(handle);
var caption = new StringBuilder(length + 1);
FindWindowApi.GetWindowText(handle, caption, caption.Capacity);
IntPtr windowPtr = FindWindowApi.FindWindowByCaption(IntPtr.Zero, caption.ToString());
if (windowPtr == IntPtr.Zero)
{
return false;
}
FindWindowApi.SendMessage(windowPtr, FindWindowApi.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
return true;
}
My Problem is now how can i get this specific window handle.
when the Thread "fillData" pops up the window. So i save the thread ID from this Thread fillData.
Now i call the other Methods with this thread id. I got the Thread ID and i get so many windows from the Process. But i need this specific window from that thread.
How can I prevent WPF window from minimizing when users clicks on show desktop button?
This link will help you : Get the minimize box click of a WPF window
you need to catch the event and handle it yourself.
Edit : This method will alert you once the state is changed, so it might not be the "best" solution but it could work.
Windows are not minimized when "Show Desktop" is issued. Instead the "WorkerW" and "Desktop" windows are brought to the foreground.
I ended up developing my own solution.
I scoured the internet for weeks trying to find an answer so I'm kind of proud of this one.
So what we do is use pinvoke to create a hook for the EVENT_SYSTEM_FOREGROUND window event.
This event triggers whenever the foreground window is changed.
Now what I noticed is when the "Show Desktop" command is issued, the WorkerW window class becomes foreground.
Note this WorkerW window is not the desktop and I confirmed the hwnd of this WorkerW window is not the Desktop hwnd.
So what we do is whenever the WorkerW window becomes the foreground, we set our "WPF Gadget Window" to be topmost!
Whenever a window other the WorkerW window becomes the foreground, we remove topmost from our "WPF Gadget Window".
If you want to take it a step further, you can uncomment out the part where I check if the new foreground window is also "PROGMAN", which is the Desktop window.
However, this will lead to your window becoming topmost if the user clicks their desktop on a different monitor. In my case, I did not want this behavior, but I figured some of you might.
Confirmed to work in Windows 10. Should work in older versions of Windows.
using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;
namespace YourNamespace
{
internal static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, ShowDesktop.WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
[DllImport("user32.dll")]
internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
[DllImport("user32.dll")]
internal static extern int GetClassName(IntPtr hwnd, StringBuilder name, int count);
}
public static class ShowDesktop
{
private const uint WINEVENT_OUTOFCONTEXT = 0u;
private const uint EVENT_SYSTEM_FOREGROUND = 3u;
private const string WORKERW = "WorkerW";
private const string PROGMAN = "Progman";
public static void AddHook(Window window)
{
if (IsHooked)
{
return;
}
IsHooked = true;
_delegate = new WinEventDelegate(WinEventHook);
_hookIntPtr = NativeMethods.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _delegate, 0, 0, WINEVENT_OUTOFCONTEXT);
_window = window;
}
public static void RemoveHook()
{
if (!IsHooked)
{
return;
}
IsHooked = false;
NativeMethods.UnhookWinEvent(_hookIntPtr.Value);
_delegate = null;
_hookIntPtr = null;
_window = null;
}
private static string GetWindowClass(IntPtr hwnd)
{
StringBuilder _sb = new StringBuilder(32);
NativeMethods.GetClassName(hwnd, _sb, _sb.Capacity);
return _sb.ToString();
}
internal delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
private static void WinEventHook(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
if (eventType == EVENT_SYSTEM_FOREGROUND)
{
string _class = GetWindowClass(hwnd);
if (string.Equals(_class, WORKERW, StringComparison.Ordinal) /*|| string.Equals(_class, PROGMAN, StringComparison.Ordinal)*/ )
{
_window.Topmost = true;
}
else
{
_window.Topmost = false;
}
}
}
public static bool IsHooked { get; private set; } = false;
private static IntPtr? _hookIntPtr { get; set; }
private static WinEventDelegate _delegate { get; set; }
private static Window _window { get; set; }
}
}
You can change your window's parent to not be affected by Show Desktop. (as stated here: Window "on desktop")
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Loaded += MainWindowLoaded;
}
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
private void MainWindowLoaded(object sender, RoutedEventArgs e)
{
var hwnd = new WindowInteropHelper(this).Handle;
var ProgmanHwnd = FindWindowEx(FindWindowEx(FindWindow("Progman", "Program Manager"), IntPtr.Zero, "SHELLDLL_DefView",""), IntPtr.Zero,"SysListView32", "FolderView");
SetParent(hwnd, ProgmanHwnd);
}
}