Related
I'm trying to capture images from my webcam by using avicap32.dll. An event should be called when a new image is available. The problem is that it only shows a black image with a few rows of colored pixels at the bottom (Sample image).
I DON'T want to use any other libraries like Aforge, DirectShow, OpenCV or something similar.
Here is my code:
public class CameraCapture2
{
[DllImport("user32", EntryPoint = "SendMessage")]
static extern bool SendMessage(int hWnd, uint wMsg, int wParam, int lParam);
[DllImport("user32", EntryPoint = "SendMessage")]
static extern int SendBitmapMessage(int hWnd, uint wMsg, int wParam, ref BITMAPINFO lParam);
[DllImport("user32", EntryPoint = "SendMessage")]
static extern int SendHeaderMessage(int hWnd, uint wMsg, int wParam, CallBackDelegate lParam);
[DllImport("avicap32.dll", EntryPoint = "capCreateCaptureWindow")]
static extern int capCreateCaptureWindow(string lpszWindowName, int dwStyle, int X, int Y, int nWidth, int nHeight, int hwndParent, int nID);
delegate void CallBackDelegate(IntPtr hwnd, ref VIDEOHEADER hdr);
CallBackDelegate delegateFrameCallBack;
AutoResetEvent autoEvent = new AutoResetEvent(false);
Thread frameThread;
public event EventHandler<NewFrameEventArgs> NewFrame;
public int FPS = 0;
public const int BitsPerPixel = 24; //RGB (by changing this value, change the other PixelFormats)
public int frameWidth = 0;
public int frameHeight = 0;
public int camID = 0;
int camHwnd, parentHwnd;
bool bStart = false;
object threadLock = new object();
int preferredFPSms;
public CameraCapture2(int frameWidth, int frameHeight, int preferredFPS, int camID, int parentHwnd)
{
this.frameWidth = frameWidth;
this.frameHeight = frameHeight;
this.parentHwnd = parentHwnd;
this.camID = camID;
PreferredFPS = preferredFPS;
delegateFrameCallBack = FrameCallBack;
}
public void Start()
{
try
{
camHwnd = capCreateCaptureWindow("WebCam", 0, 0, 0, frameWidth, frameHeight, parentHwnd, camID);
// connect to the device
if (SendMessage(camHwnd, WM_CAP.WM_CAP_DRIVER_CONNECT, 0, 0))
{
BITMAPINFO bInfo = new BITMAPINFO();
bInfo.bmiHeader = new BITMAPINFOHEADER();
bInfo.bmiHeader.biSize = (uint)Marshal.SizeOf(bInfo.bmiHeader);
bInfo.bmiHeader.biWidth = frameWidth;
bInfo.bmiHeader.biHeight = frameHeight;
bInfo.bmiHeader.biPlanes = 1;
bInfo.bmiHeader.biBitCount = BitsPerPixel; // bits per pixel, 24 - RGB
//Enable preview mode. In preview mode, frames are transferred from the
//capture hardware to system memory and then displayed in the capture
//window using GDI functions.
SendMessage(camHwnd, WM_CAP.WM_CAP_SET_PREVIEW, 1, 0);
SendMessage(camHwnd, WM_CAP.WM_CAP_SET_PREVIEWRATE, 34, 0); // sets the frame display rate in preview mode
SendBitmapMessage(camHwnd, WM_CAP.WM_CAP_SET_VIDEOFORMAT, Marshal.SizeOf(bInfo), ref bInfo);
frameThread = new Thread(new ThreadStart(this.FrameGrabber));
bStart = true; // First, set variable
frameThread.Priority = ThreadPriority.Lowest;
frameThread.Start(); // Only then put thread to the queue
}
else
throw new Exception("Cannot connect to device");
}
catch (Exception e)
{
Stop();
throw new Exception("Error: " + e.Message);
}
}
public void Stop()
{
try
{
bStart = false;
Set();
SendMessage(camHwnd, WM_CAP.WM_CAP_DRIVER_DISCONNECT, 0, 0);
}
catch { }
}
private void FrameGrabber()
{
while (bStart) // if worker active thread is still required
{
try
{
// get the next frame. This is the SLOWEST part of the program
SendMessage(camHwnd, WM_CAP.WM_CAP_GRAB_FRAME_NOSTOP, 0, 0);
SendHeaderMessage(camHwnd, WM_CAP.WM_CAP_SET_CALLBACK_FRAME, 0, delegateFrameCallBack);
}
catch (Exception excep)
{
this.Stop(); // stop the process
throw new Exception("Capturing error:\r\n" + excep.Message);
}
}
}
/// <summary>
/// Allow waiting worker (FrameGrabber) thread to proceed
/// </summary>
public void Set()
{
autoEvent.Set();
}
private void FrameCallBack(IntPtr hwnd, ref VIDEOHEADER hdr)
{
if (NewFrame != null)
{
//Bitmap bmp = new Bitmap(frameWidth, frameHeight, frameWidth * 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, hdr.lpData);
byte[] _imageTemp = new byte[hdr.dwBufferLength];
Marshal.Copy(hdr.lpData, _imageTemp, 0, (int)hdr.dwBufferLength);
Bitmap bmp = new Bitmap(new System.IO.MemoryStream(WriteBitmapFile(frameWidth, frameHeight, _imageTemp)));
NewFrame(this, new NewFrameEventArgs(bmp));
}
// block thread for preferred milleseconds
if (preferredFPSms == 0)
autoEvent.WaitOne();
else
autoEvent.WaitOne(preferredFPSms, false);
}
private static byte[] WriteBitmapFile(int width, int height, byte[] imageData)
{
using (var stream = new System.IO.MemoryStream(imageData))
using (var bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format24bppRgb))
{
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height)
, System.Drawing.Imaging.ImageLockMode.WriteOnly
, bmp.PixelFormat
);
Marshal.Copy(imageData, 0, bmpData.Scan0, imageData.Length);
bmp.UnlockBits(bmpData);
if (bmp == null)
return null;
bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
bmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png); // get bitmap bytes
return ms.ToArray();
} // End Using stream
}
}
public void ShowVideoDialog()
{
SendMessage(camHwnd, WM_CAP.WM_CAP_DLG_VIDEODISPLAY, 0, 0);
}
public int PreferredFPS
{
get { return 1000 / preferredFPSms; }
set
{
if (value == 0)
preferredFPSms = 0;
else if (value > 0 && value <= 30)
{
preferredFPSms = 1000 / value;
}
}
}
}
public class NewFrameEventArgs
{
public Bitmap Frame;
public NewFrameEventArgs(Bitmap frame)
{
Frame = frame;
}
}
Most of this code is from Codeproject
I am creating a form inside another form. In the child form I have created taskbar button. I am overriding WndProc in the button.I am referring https://www.codeproject.com/Articles/10171/Adding-a-Minimize-to-tray-button-to-a-Form-s-caption bar. But whenever I am getting messages(mouse coordinates) , it is given with respect to the parent form. This makes trouble in calculations .I want those with respect to child form.How to achieve this?
This is the code for form1
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Form2 form2 = new Form2();
form2.TopLevel = false;
form2.AutoScroll = true;
form2.Anchor = AnchorStyles.Top;
form2.Dock = DockStyle.Fill;
this.splitContainer1.Panel2.Controls.Add(form2);
form2.Show();
}
}
this is form2.
public partial class Form2 : Form
{
TyronM.MinTrayBtn Pin_Button;
public Form2()
{
InitializeComponent();
Pin_Button = new TyronM.MinTrayBtn(this);
Pin_Button.MinTrayBtnClicked += new TyronM.MinTrayBtnClickedEventHandler(this.Pin_Button_Clicked);
}
public void Pin_Button_Clicked(object sender, EventArgs e)
{
MessageBox.Show("Pin button got Clicked");
}
protected override void WndProc(ref Message message)
{
const int WM_SYSCOMMAND = 0x0112;
const int SC_MOVE = 0xF010;
switch (message.Msg)
{
case WM_SYSCOMMAND:
int command = message.WParam.ToInt32() & 0xfff0;
if (command == SC_MOVE)
return;
break;
}
base.WndProc(ref message);
}
}
And this is for button.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Diagnostics;
using System.Resources;
using System.Runtime.InteropServices;
namespace TyronM {
public delegate void MinTrayBtnClickedEventHandler(object sender, EventArgs e);
/// <summary>
/// Summary description for Class.
/// </summary>
public class MinTrayBtn : NativeWindow {
bool pinned = false;
bool pressed = false;
Size wnd_size = new Size();
public bool captured;
Form parent;
public event MinTrayBtnClickedEventHandler MinTrayBtnClicked;
#region Constants
const int WM_SIZE = 5;
const int WM_SYNCPAINT = 136;
const int WM_MOVE = 3;
const int WM_ACTIVATE = 6;
const int WM_LBUTTONDOWN =513;
const int WM_LBUTTONUP =514;
const int WM_LBUTTONDBLCLK =515;
const int WM_MOUSEMOVE = 512;
const int WM_PAINT = 15;
const int WM_GETTEXT = 13;
const int WM_NCCREATE =129;
const int WM_NCLBUTTONDOWN = 161;
const int WM_NCLBUTTONUP = 162;
const int WM_NCMOUSEMOVE = 160;
const int WM_NCACTIVATE =134;
const int WM_NCPAINT = 133;
const int WM_NCHITTEST = 132;
const int WM_NCLBUTTONDBLCLK = 163;
const int VK_LBUTTON = 1;
const int SM_CXSIZE = 30;
const int SM_CYSIZE = 31;
#endregion
#region Extra Constants
const int WM_MBUTTONUP = 0x0208;
#endregion
#region WinAPI Imports
[DllImport("user32")]
public static extern int GetWindowDC(int hwnd);
[DllImport("user32")]
public static extern short GetAsyncKeyState(int vKey);
[DllImport("user32")]
public static extern int SetCapture(int hwnd);
[DllImport("user32")]
public static extern bool ReleaseCapture();
[DllImport("user32")]
public static extern int GetSysColor(int nIndex);
[DllImport("user32")]
public static extern int GetSystemMetrics(int nIndex);
#endregion
#region Constructor and Handle-Handler ^^
public MinTrayBtn(Form parent) {
parent.HandleCreated += new EventHandler(this.OnHandleCreated);
parent.HandleDestroyed+= new EventHandler(this.OnHandleDestroyed);
parent.TextChanged+= new EventHandler(this.OnTextChanged);
this.parent = parent;
}
// Listen for the control's window creation and then hook into it.
internal void OnHandleCreated(object sender, EventArgs e){
// Window is now created, assign handle to NativeWindow.
AssignHandle(((Form)sender).Handle);
}
internal void OnHandleDestroyed(object sender, EventArgs e) {
// Window was destroyed, release hook.
ReleaseHandle();
}
// Changing the Text invalidates the Window, so we got to Draw the Button again
private void OnTextChanged(object sender, EventArgs e) {
DrawButton();
}
#endregion
#region WndProc
[System.Security.Permissions.PermissionSet(System.Security.Permissions.SecurityAction.Demand, Name="FullTrust")]
protected override void WndProc(ref Message m){
//label3.Text = "Button pressed: " + pressed;
//label4.Text = "Mouse captured: " + captured;
// Change the Pressed-State of the Button when the User pressed the
// left mouse button and moves the cursor over the button
if (m.Msg == WM_LBUTTONDBLCLK ||m.Msg == WM_LBUTTONDOWN || m.Msg == WM_LBUTTONUP
||m.Msg == WM_MBUTTONUP
)
MessageBox.Show("click happened");
if(m.Msg==WM_MOUSEMOVE) {
Point pnt2 = new Point((int)m.LParam);
Size rel_pos2 = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
// Not needed because SetCapture seems to convert the cordinates anyway
//pnt2 = PointToClient(pnt2);
pnt2-=rel_pos2;
//label2.Text = "Cursor #"+pnt2.X+"/"+pnt2.Y;
if(pressed) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(!MouseinBtn(pnt)) {
pressed = false;
DrawButton();
}
} else {
if((GetAsyncKeyState(VK_LBUTTON)&(-32768))!=0) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
//pnt = PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
pressed = true;
DrawButton();
}
}
}
}
// Ignore Double-Clicks on the Traybutton
if(m.Msg==WM_NCLBUTTONDBLCLK) {
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
return;
}
}
#region NOT WORKING
// Button released and eventually clicked
if(m.Msg==WM_LBUTTONUP)
{
ReleaseCapture();
captured = false;
if(pressed) {
pressed = false;
DrawButton();
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
//TrayButton_clicked();
EventArgs e = new EventArgs();
if (MinTrayBtnClicked != null)
MinTrayBtnClicked(this, e);
return;
}
}
}
#endregion
// Clicking the Button - Capture the Mouse and await until the Uses relases the Button again
if(m.Msg==WM_NCLBUTTONDOWN) {
//MessageBox.Show("clicked");
Point pnt = new Point((int)m.LParam);
Size rel_pos = new Size(parent.PointToClient(new Point(parent.Location.X, parent.Location.Y)));
pnt = parent.PointToClient(pnt);
pnt-=rel_pos;
if(MouseinBtn(pnt)) {
MessageBox.Show("clicked");
pressed = true;
DrawButton();
SetCapture((int)parent.Handle);
captured = true;
return;
}
}
// Drawing the Button and getting the Real Size of the Window
if(m.Msg == WM_ACTIVATE || m.Msg==WM_SIZE || m.Msg==WM_SYNCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCCREATE || m.Msg==WM_NCPAINT || m.Msg==WM_NCACTIVATE || m.Msg==WM_NCHITTEST || m.Msg==WM_PAINT) {
if(m.Msg==WM_SIZE) wnd_size = new Size(new Point((int)m.LParam));
DrawButton();
}
base.WndProc(ref m);
}
#endregion
#region Button-Specific Functions
public bool MouseinBtn(Point click) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
Size btn_size = new Size(btn_width, btn_height);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
return click.X>=pos.X && click.X<=pos.X+btn_size.Width &&
click.Y>=pos.Y && click.Y<=pos.Y+btn_size.Height;
}
public void DrawButton() {
Graphics g = Graphics.FromHdc((IntPtr)GetWindowDC((int)parent.Handle)); //m.HWnd));
DrawButton(g, pressed);
}
public void DrawButton(Graphics g, bool pressed) {
int btn_width = GetSystemMetrics(SM_CXSIZE);
int btn_height = GetSystemMetrics(SM_CYSIZE);
//Point pos = new Point(wnd_size.Width-3*btn_width-12-(btn_width-18),6);
Point pos = new Point(wnd_size.Width - 3 * btn_width - 12 - (btn_width - 18)+7, 6+4);
// real button size
btn_width-=2;
btn_height-=4;
Color light = SystemColors.ControlLightLight;
Color icon = SystemColors.ControlText;
Color background = SystemColors.Control;
Color shadow1 = SystemColors.ControlDark;
Color shadow2 = SystemColors.ControlDarkDark;
Color tmp1, tmp2;
if(pressed) {
tmp1 = shadow2;
tmp2 = light;
} else {
tmp1 = light;
tmp2 = shadow2;
}
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X+btn_width-1,pos.Y));
g.DrawLine(new Pen(tmp1),pos, new Point(pos.X,pos.Y+btn_height-1));
if(pressed) {
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+btn_width-2, pos.Y+1);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+1, pos.X+1, pos.Y+btn_height-2);
} else {
g.DrawLine(new Pen(shadow1),pos.X+btn_width-2, pos.Y+1, pos.X+btn_width-2, pos.Y+btn_height-2);
g.DrawLine(new Pen(shadow1),pos.X+1, pos.Y+btn_height-2, pos.X+btn_width-2, pos.Y+btn_height-2);
}
g.DrawLine(new Pen(tmp2),pos.X+btn_width-1, pos.Y+0, pos.X+btn_width-1, pos.Y+btn_height-1);
g.DrawLine(new Pen(tmp2),pos.X+0, pos.Y+btn_height-1, pos.X+btn_width-1, pos.Y+btn_height-1);
g.FillRectangle(new SolidBrush(background),pos.X+1+Convert.ToInt32(pressed), pos.Y+1+Convert.ToInt32(pressed), btn_width-3,btn_height-3);
#region Added Code
g.FillRectangle(new SolidBrush(icon),pos.X+(float)0.5625*btn_width+Convert.ToInt32(pressed),pos.Y+(float)0.6428*btn_height+Convert.ToInt32(pressed),btn_width*(float)0.1875,btn_height*(float)0.143);
g.DrawImage(Image.FromFile("red_Pushpin.jfif"),new Rectangle( pos,new Size(btn_width,btn_height)));
#endregion
}
#endregion
}
}
This is code for main
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
This is the screenshot of the winform.
EDIT:
Upon investigation I found that WM_LBUTTONUP(Mouse left button up) is not triggered. Or it is not coming inside WndProc.
EDIT1:
It seems there was a problem in brackets.I changed the if conditions into switch case. This somewhat cleared the "Mouse left button up" problem. Now it is properly triggering. The remaining problem is mouse points based on parent form.
I wrote a simple class to create a window and initialize OpenGL to that window. It works fine in native c++ and in CLI as well. But as soon as I import the CLI class as DLL into my C# project the rendering fails. A blank window shows up but the rendering doesn't work. The SwapBuffers and the GL rendering get called but nothing happens. Do you have any idea what can be wrong?
(Sorry I am not a native speaker)
OpenGLClass.cpp
#include "OpenGLClass.h"
#include <iostream>
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
OpenGLClass::OpenGLClass()
{
}
OpenGLClass::~OpenGLClass()
{
}
bool OpenGLClass::Initialize()
{
if (InitWindow())
return InitOGL();
else
return false;
}
void OpenGLClass::Run()
{
MSG msg = { 0 };
while (true)
{
if (PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
Render();
SwapBuffers(m_hDevContext);
}
}
}
void OpenGLClass::Render()
{
glViewport(0, 0, 800, 600);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, (float)800 / (float)600, 1, 1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0, 0, 10,
0, 0, 0,
0, 1, 0);
glClearColor(0.2, 0.5, 0.8, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static float i = 0.01f;
i += 0.001f;
float c = cos(i);
float s = sin(i);
glBegin(GL_TRIANGLES);
glColor3f(c, 0, 0); // red
glVertex3f(1 + c, 0 + s, 0);
glColor3f(c, s, 0); // yellow
glVertex3f(0 + c, 1 + s, 0);
glColor3f(s, 0.1f, s); // magenta
glVertex3f(-1 + c, 0 + s, 0);
glEnd();
}
bool OpenGLClass::InitWindow()
{
WNDCLASSEX wc;
m_hInstance = GetModuleHandle(NULL);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = m_hInstance;
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = wc.hIcon;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "OpenGLWindow";
wc.cbSize = sizeof(WNDCLASSEX);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, "RegisterClassEx() failed", "Error", MB_OK);
return false;
}
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
screenWidth = 800;
screenHeight = 600;
int nStyle = WS_POPUP;
m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, "OpenGLWindow", "WindowTitle", nStyle, 0, 0, screenWidth, screenHeight, NULL, NULL, m_hInstance, NULL);
ShowWindow(m_hwnd, SW_SHOW);
return true;
}
bool OpenGLClass::InitOGL()
{
m_hDevContext = GetDC(m_hwnd);
PIXELFORMATDESCRIPTOR pfd = { 0 };
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 32;
pfd.cDepthBits = 24;
pfd.cStencilBits = 24;
int format = ChoosePixelFormat(m_hDevContext, &pfd);
if (format == 0)
return false;
std::cout << "Pixel Format: " << format << std::endl;
if (!SetPixelFormat(m_hDevContext, format, &pfd))
return false;
m_GLRenderContext = wglCreateContext(m_hDevContext);
if (!wglMakeCurrent(m_hDevContext, m_GLRenderContext))
return false;
GLint GlewInitResult = glewInit();
if (GLEW_OK != GlewInitResult)
{
return false;
}
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
return true;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_KEYDOWN:
if (wParam == VK_ESCAPE)
{
PostQuitMessage(0);
DestroyWindow(hwnd);
}
break;
case WM_CLOSE:
PostQuitMessage(0);
DestroyWindow(hwnd);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
WrapperClass.h
#pragma once
#include <OpenGLClass.h>
public ref class WrapperClass
{
public:
WrapperClass() : m_Impl(new OpenGLClass) {}
~WrapperClass() {
delete m_Impl;
}
protected:
!WrapperClass() {
delete m_Impl;
}
public:
inline bool Initialize()
{
return m_Impl->Initialize();
}
inline void Run()
{
m_Impl->Run();
}
private:
OpenGLClass* m_Impl;
};
The C# code
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
BackgroundWorker bw = new BackgroundWorker();
WrapperClass wc;
public Form1()
{
InitializeComponent();
InitOGL();
}
private void InitOGL()
{
wc = new WrapperClass();
if(wc.Initialize())
{
bw.DoWork += Bw_DoWork;
bw.RunWorkerAsync();
}
}
private void Bw_DoWork(object sender, DoWorkEventArgs e)
{
wc.Run();
}
}
}
As BDL and starmole pointed out, I was running the InitOGL and the Render on different thread.
I've made a simple appBar with just a label on the top of the screen that shrinks the desktop but I'm having trouble making it appear my second monitor. I've been searching around but everything I've found has been for WPF. These are most likely the areas where I've made a mistake but if there is any other code you need to see, just let me know.
private void InitializeComponent()
{
this.ClientSize = new System.Drawing.Size(SystemInformation.WorkingArea.Width, -1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "MainForm";
this.Text = "AppBar";
this.Closing += new System.ComponentModel.CancelEventHandler(this.OnClosing);
this.Load += new System.EventHandler(this.OnLoad);
this.BackColor = Color.Green;
this.Padding = new Padding(0, 0, 0, 0);
Label label1 = new Label();
label1.Text = "TEXT";
label1.Width = 270;
label1.Margin = new Padding(0,0,0,0);
label1.Padding = new Padding(0,0,0,0);
label1.TextAlign = ContentAlignment.MiddleCenter;
label1.ForeColor = Color.White;
label1.Font = new Font(FontFamily.GenericSansSerif, 12,FontStyle.Regular);
label1.Location = new Point((SystemInformation.WorkingArea.Width - 270) / 2, 0);
this.Controls.Add(label1);
}
private void ABSetPos()
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
abd.uEdge = (int)ABEdge.ABE_TOP;
if (abd.uEdge == (int)ABEdge.ABE_LEFT || abd.uEdge == (int)ABEdge.ABE_RIGHT)
{
abd.rc.top = 0;
abd.rc.bottom = SystemInformation.PrimaryMonitorSize.Height;
if (abd.uEdge == (int)ABEdge.ABE_LEFT)
{
abd.rc.left = 0;
abd.rc.right = Size.Width;
}
else
{
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width;
abd.rc.left = abd.rc.right - Size.Width;
}
}
else
{
abd.rc.left = 0;
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width;
if (abd.uEdge == (int)ABEdge.ABE_TOP)
{
abd.rc.top = 0;
abd.rc.bottom = Size.Height;
}
else
{
abd.rc.bottom = SystemInformation.PrimaryMonitorSize.Height;
abd.rc.top = abd.rc.bottom - Size.Height;
}
}
You can use a different screen by iterating over the Screen.AllScreens array. For example, here is how you would get the first non-primary monitor:
Screen nonPrimaryScreen = Screen.AllScreens.FirstOrDefault(x => !x.Primary);
Then everywhere you are using SystemInformation.WorkingArea (which always uses the primary screen), you can use:
nonPrimaryScreen.WorkingArea
Assuming nonPrimaryScreen != null ... of course.
EDIT:
Instead of duplicating code, make it all more generic:
public static Rectangle GetWorkingArea() {
if (UseWantsItOnPrimaryScreen) {
return SystemInformation.WorkingArea;
}
else {
return Screen.AllScreens.FirstOrDefault(x => !x.Primary).WorkingArea;
}
}
private Screen GetScreenObject(String Name)
{
logger.Info(GlobalModulename + "# ScreenList::looking for screen:"+Name);
if ((Name == "Primary"))
{
bool ExpectedParameter = true;
foreach (var screen in Screen.AllScreens)
{
// For each screen, add the screen properties to a list box.
logger.Info(GlobalModulename + "# ScreenList::("+screen.DeviceName.ToString()+")Primary Screen: " + screen.Primary.ToString());
if (screen.Primary==ExpectedParameter)
{
return screen;
}
}
}
if ((Name == "Secondary"))
{
bool ExpectedParameter = false;
foreach (var screen in Screen.AllScreens)
{
// For each screen, add the screen properties to a list box.
logger.Info(GlobalModulename + "# ScreenList::(" + screen.DeviceName.ToString() + ")Primary Screen: " + screen.Primary.ToString());
if (screen.Primary == ExpectedParameter)
{
return screen;
}
}
}
// konkretni jmeno obrazovky tak jak je to v systemu
try
{
foreach (var screen in Screen.AllScreens)
{
// For each screen, add the screen properties to a list box.
logger.Info("UEFA_Core # ScreenList::Device Name: " + screen.DeviceName);
logger.Info("UEFA_Core # ScreenList::Bounds: " + screen.Bounds.ToString());
logger.Info("UEFA_Core # ScreenList::Type: " + screen.GetType().ToString());
logger.Info("UEFA_Core # ScreenList::Working Area: " + screen.WorkingArea.ToString());
logger.Info("UEFA_Core # ScreenList::Primary Screen: " + screen.Primary.ToString());
if (screen.DeviceName == Name) return screen;
}
}
catch { }
// podobne jmeno obrazovky tak jak je to v systemu
try
{
foreach (var screen in Screen.AllScreens)
{
// For each screen, add the screen properties to a list box.
logger.Info("UEFA_Core # ScreenList::Device Name: " + screen.DeviceName);
logger.Info("UEFA_Core # ScreenList::Bounds: " + screen.Bounds.ToString());
logger.Info("UEFA_Core # ScreenList::Type: " + screen.GetType().ToString());
logger.Info("UEFA_Core # ScreenList::Working Area: " + screen.WorkingArea.ToString());
logger.Info("UEFA_Core # ScreenList::Primary Screen: " + screen.Primary.ToString());
if (screen.DeviceName.Contains(Name)) return screen;
}
}
catch { }
logger.Info("UEFA_Core # ScreenList::No screen found by name");
return Screen.PrimaryScreen;
}
#region APPBAR
[StructLayout(LayoutKind.Sequential)]
struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
struct APPBARDATA
{
public int cbSize;
public IntPtr hWnd;
public int uCallbackMessage;
public int uEdge;
public RECT rc;
public IntPtr lParam;
}
enum ABMsg : int
{
ABM_NEW = 0,
ABM_REMOVE = 1,
ABM_QUERYPOS = 2,
ABM_SETPOS = 3,
ABM_GETSTATE = 4,
ABM_GETTASKBARPOS = 5,
ABM_ACTIVATE = 6,
ABM_GETAUTOHIDEBAR = 7,
ABM_SETAUTOHIDEBAR = 8,
ABM_WINDOWPOSCHANGED = 9,
ABM_SETSTATE = 10
}
enum ABNotify : int
{
ABN_STATECHANGE = 0,
ABN_POSCHANGED,
ABN_FULLSCREENAPP,
ABN_WINDOWARRANGE
}
enum ABEdge : int
{
ABE_LEFT = 0,
ABE_TOP,
ABE_RIGHT,
ABE_BOTTOM
}
private bool fBarRegistered = false;
[DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);
[DllImport("USER32")]
static extern int GetSystemMetrics(int Index);
[DllImport("User32.dll", ExactSpelling = true,
CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool MoveWindow
(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
private static extern int RegisterWindowMessage(string msg);
private int uCallBack;
private void RegisterBar()
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
if (!fBarRegistered)
{
uCallBack = RegisterWindowMessage("AppBarMessage");
abd.uCallbackMessage = uCallBack;
uint ret = SHAppBarMessage((int)ABMsg.ABM_NEW, ref abd);
fBarRegistered = true;
ABSetPos();
}
else
{
SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref abd);
fBarRegistered = false;
}
}
private void ABSetPos()
{
APPBARDATA abd = new APPBARDATA();
abd.cbSize = Marshal.SizeOf(abd);
abd.hWnd = this.Handle;
abd.uEdge = (int)ABEdge.ABE_TOP;
if (abd.uEdge == (int)ABEdge.ABE_LEFT || abd.uEdge == (int)ABEdge.ABE_RIGHT)
{
abd.rc.top = this.GetScreenObject(ScreenName).Bounds.Top; //0;
abd.rc.bottom = this.GetScreenObject(ScreenName).Bounds.Top + this.GetScreenObject(ScreenName).Bounds.Height; //SystemInformation.PrimaryMonitorSize.Height;
if (abd.uEdge == (int)ABEdge.ABE_LEFT)
{
abd.rc.left = this.GetScreenObject(ScreenName).Bounds.Left;//0;
abd.rc.right = Size.Width;
}
else
{
abd.rc.right = this.GetScreenObject(ScreenName).Bounds.Left + this.GetScreenObject(ScreenName).Bounds.Width; // SystemInformation.PrimaryMonitorSize.Width;
abd.rc.left = abd.rc.right - Size.Width;
}
}
else
{
abd.rc.left = this.GetScreenObject(ScreenName).Bounds.Left; //0;
abd.rc.right = this.GetScreenObject(ScreenName).Bounds.Left+this.GetScreenObject(ScreenName).Bounds.Width; //SystemInformation.PrimaryMonitorSize.Width;
if (abd.uEdge == (int)ABEdge.ABE_TOP)
{
abd.rc.top = this.GetScreenObject(ScreenName).Bounds.Top; //0 nebo -1080
abd.rc.bottom = Size.Height;
}
else
{
abd.rc.bottom = this.GetScreenObject(ScreenName).Bounds.Top + this.GetScreenObject(ScreenName).Bounds.Height; //SystemInformation.PrimaryMonitorSize.Height;
abd.rc.top = abd.rc.bottom - Size.Height;
}
}
// Query the system for an approved size and position.
SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref abd);
// Adjust the rectangle, depending on the edge to which the
// appbar is anchored.
switch (abd.uEdge)
{
case (int)ABEdge.ABE_LEFT:
abd.rc.right = abd.rc.left + Size.Width;
break;
case (int)ABEdge.ABE_RIGHT:
abd.rc.left = abd.rc.right - Size.Width;
break;
case (int)ABEdge.ABE_TOP:
abd.rc.bottom = abd.rc.top + Size.Height;
break;
case (int)ABEdge.ABE_BOTTOM:
abd.rc.top = abd.rc.bottom - Size.Height;
break;
}
// Pass the final bounding rectangle to the system.
SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref abd);
// Move and size the appbar so that it conforms to the
// bounding rectangle passed to the system.
MoveWindow(abd.hWnd, abd.rc.left, abd.rc.top,
abd.rc.right - abd.rc.left, abd.rc.bottom - abd.rc.top, true);
}
protected override void WndProc(ref System.Windows.Forms.Message m)
{
if (m.Msg == uCallBack)
{
switch (m.WParam.ToInt32())
{
case (int)ABNotify.ABN_POSCHANGED:
ABSetPos();
break;
}
}
try
{
base.WndProc(ref m);
}
catch (Exception E) { }
}
protected override System.Windows.Forms.CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= (~0x00C00000); // WS_CAPTION
cp.Style &= (~0x00800000); // WS_BORDER
//cp.ExStyle = 0x00000080 | 0x00000008 | 0x20; // WS_EX_TOOLWINDOW | WS_EX_TOPMOST
//cp.ExStyle &= 0x20;
cp.ExStyle |= 0x00000008 | 0x00000080;
//cp.ExStyle &= 0x00000080 ; // WS_EX_TOOLWINDOW | WS_EX_TOPMOST
return cp;
}
}
private void OnLoad(object sender, System.EventArgs e)
{
RegisterBar();
}
private void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
RegisterBar();
}
#endregion
You can compute correct formula for moving your AppBar to second monitor without using anything other than PrimaryMonitorSize. For example for left side AppBar on second monitor you can use this:
if (abd.uEdge == (int)ABEdge.ABE_LEFT)
{
abd.rc.left = SystemInformation.PrimaryMonitorSize.Width;
abd.rc.right = SystemInformation.PrimaryMonitorSize.Width + Size.Width;
}
I need to append text to RichTextBox, and need to perform it without making text box scroll or lose current text selection, is it possible?
The RichTextBox in WinForms is quite flicker happy when you play around with the text and select-text methods.
I have a standard replacement to turn off the painting and scrolling with the following code:
class RichTextBoxEx: RichTextBox
{
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
const int WM_USER = 0x400;
const int WM_SETREDRAW = 0x000B;
const int EM_GETEVENTMASK = WM_USER + 59;
const int EM_SETEVENTMASK = WM_USER + 69;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
Point _ScrollPoint;
bool _Painting = true;
IntPtr _EventMask;
int _SuspendIndex = 0;
int _SuspendLength = 0;
public void SuspendPainting()
{
if (_Painting)
{
_SuspendIndex = this.SelectionStart;
_SuspendLength = this.SelectionLength;
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
_EventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
_Painting = false;
}
}
public void ResumePainting()
{
if (!_Painting)
{
this.Select(_SuspendIndex, _SuspendLength);
SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(this.Handle, EM_SETEVENTMASK, 0, _EventMask);
SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
_Painting = true;
this.Invalidate();
}
}
}
and then from my form, I can happily have a flicker-free richtextbox control:
richTextBoxEx1.SuspendPainting();
richTextBoxEx1.AppendText("Hey!");
richTextBoxEx1.ResumePainting();
based on LarsTech article here something nice:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
namespace yournamespace
{
class RichTextBoxEx : RichTextBox
{
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
const int WM_USER = 0x400;
const int WM_SETREDRAW = 0x000B;
const int EM_GETEVENTMASK = WM_USER + 59;
const int EM_SETEVENTMASK = WM_USER + 69;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
Point _ScrollPoint;
bool _Painting = true;
IntPtr _EventMask;
int _SuspendIndex = 0;
int _SuspendLength = 0;
public bool Autoscroll = true;
public void SuspendPainting()
{
if (_Painting)
{
_SuspendIndex = this.SelectionStart;
_SuspendLength = this.SelectionLength;
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
_EventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
_Painting = false;
}
}
public void ResumePainting()
{
if (!_Painting)
{
this.Select(_SuspendIndex, _SuspendLength);
SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref _ScrollPoint);
SendMessage(this.Handle, EM_SETEVENTMASK, 0, _EventMask);
SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
_Painting = true;
this.Invalidate();
}
}
new public void AppendText(string text) // overwrites RichTextBox.AppendText
{
if (Autoscroll)
base.AppendText(text);
else
{
SuspendPainting();
base.AppendText(text);
ResumePainting();
}
}
}
}
you can use it like this:
var textbox = new RichTextBoxEx();
textbox.Autoscroll = false;
textbox.AppendText("Hi");
This solution is almost spot on, except that it does not correctly handle reverse selections (where the caret is at the start of the selection, not the end - e.g. SHIFT+LEFT or an upwards mouse drag is used to select text).
Here's an improved version, with the following added features:
If the caret is at the end of the text, it remains there, scrolling if required.
If the original selection started or ended on the last character, any appended text is included in the new selection.
This means you can put the caret at the end of the text and monitor text being added (think log file monitoring). It also means you can do CTRL+A to select all, and have any appended text automatically included in your selection.
class RichTextBoxEx : RichTextBox
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
[DllImport("user32")]
private static extern int GetCaretPos(out Point p);
const int WM_USER = 0x400;
const int WM_SETREDRAW = 0x000B;
const int EM_GETEVENTMASK = WM_USER + 59;
const int EM_SETEVENTMASK = WM_USER + 69;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
private Point oScrollPoint;
private bool bPainting = true;
private IntPtr oEventMask;
private int iSuspendCaret;
private int iSuspendIndex;
private int iSuspendLength;
private bool bWasAtEnd;
public int CaretIndex
{
get
{
Point oCaret;
GetCaretPos(out oCaret);
return this.GetCharIndexFromPosition(oCaret);
}
}
public void AppendTextWithoutScroll(string text)
{
this.SuspendPainting();
this.AppendText(text);
this.ResumePainting();
}
private void SuspendPainting()
{
if (this.bPainting)
{
this.iSuspendCaret = this.CaretIndex;
this.iSuspendIndex = this.SelectionStart;
this.iSuspendLength = this.SelectionLength;
this.bWasAtEnd = this.iSuspendIndex + this.iSuspendLength == this.TextLength;
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref this.oScrollPoint);
SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
this.oEventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
this.bPainting = false;
}
}
private void ResumePainting()
{
if (!this.bPainting)
{
if (this.iSuspendLength == 0)
{
if (!bWasAtEnd)
{
this.Select(this.iSuspendIndex, 0);
}
}
else
{
// Original selection was to end of text
if (bWasAtEnd)
{
// Maintain end of selection at end of new text
this.iSuspendLength = this.TextLength - this.iSuspendIndex;
}
if (this.iSuspendCaret > this.iSuspendIndex)
{
// Forward select (caret is at end)
this.Select(this.iSuspendIndex, this.iSuspendLength);
}
else
{
// Reverse select (caret is at start)
this.Select(this.iSuspendIndex + this.iSuspendLength, -this.iSuspendLength);
}
}
SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref this.oScrollPoint);
SendMessage(this.Handle, EM_SETEVENTMASK, 0, this.oEventMask);
SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
this.bPainting = true;
this.Invalidate();
}
}
}
Modified LarsTech code to automatically stop auto scrolling if caret is not at the last position in the RichTextBox. Also solved the problem with entering colored text. To resume scrolling put caret at the last position (Ctrl-END)
class RichTextBoxEx : RichTextBox
{
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, ref Point lParam);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, Int32 wMsg, Int32 wParam, IntPtr lParam);
[DllImport("user32")]
private static extern int GetCaretPos(out Point p);
const int WM_USER = 0x400;
const int WM_SETREDRAW = 0x000B;
const int EM_GETEVENTMASK = WM_USER + 59;
const int EM_SETEVENTMASK = WM_USER + 69;
const int EM_GETSCROLLPOS = WM_USER + 221;
const int EM_SETSCROLLPOS = WM_USER + 222;
private Point oScrollPoint;
private bool bPainting = true;
private IntPtr oEventMask;
private int iSuspendCaret;
private int iSuspendIndex;
private int iSuspendLength;
private bool bWasAtEnd;
private Color _selColor = Color.Black;
public int CaretIndex
{
get
{
Point oCaret;
GetCaretPos(out oCaret);
return this.GetCharIndexFromPosition(oCaret);
}
}
new public Color SelectionColor { get { return _selColor; } set { _selColor = value; } }
new public void AppendText(string text) // overwrites RichTextBox.AppendText
{
if (this.SelectionStart >= this.TextLength)
{
base.SelectionColor = _selColor;
base.AppendText(text);
}
else
{
var selStart = this.SelectionStart;
var selLength = this.SelectionLength;
SuspendPainting();
this.Select(this.TextLength, 0);
base.SelectionColor = _selColor;
base.AppendText(text);
this.Select(selStart, selLength);
ResumePainting();
}
}
private void SuspendPainting()
{
if (this.bPainting)
{
this.iSuspendCaret = this.CaretIndex;
this.iSuspendIndex = this.SelectionStart;
this.iSuspendLength = this.SelectionLength;
this.bWasAtEnd = this.iSuspendIndex + this.iSuspendLength == this.TextLength;
SendMessage(this.Handle, EM_GETSCROLLPOS, 0, ref this.oScrollPoint);
SendMessage(this.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
this.oEventMask = SendMessage(this.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
this.bPainting = false;
}
}
private void ResumePainting()
{
if (!this.bPainting)
{
if (this.iSuspendLength == 0)
{
if (!bWasAtEnd)
{
this.Select(this.iSuspendIndex, 0);
}
}
else
{
// Original selection was to end of text
if (bWasAtEnd)
{
// Maintain end of selection at end of new text
this.iSuspendLength = this.TextLength - this.iSuspendIndex;
}
if (this.iSuspendCaret > this.iSuspendIndex)
{
// Forward select (caret is at end)
this.Select(this.iSuspendIndex, this.iSuspendLength);
}
else
{
// Reverse select (caret is at start)
this.Select(this.iSuspendIndex + this.iSuspendLength, -this.iSuspendLength);
}
}
SendMessage(this.Handle, EM_SETSCROLLPOS, 0, ref this.oScrollPoint);
SendMessage(this.Handle, EM_SETEVENTMASK, 0, this.oEventMask);
SendMessage(this.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
this.bPainting = true;
this.Invalidate();
}
}
}
This should do what you want:
Dim tempStart As Int32
Dim tempLength As Int32
tempStart = RichTextBox1.SelectionStart
tempLength = RichTextBox1.SelectionLength
RichTextBox1.Text += "dsfkljwerhsdlf"
RichTextBox1.SelectionStart = tempStart
RichTextBox1.SelectionLength = tempLength