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
Related
I am making a recorder that records a specific window the user chooses. While I am recording, the program forces the window to be active all the time. It does not let me minimize it or even move it. It forced me to use alt + f4(that kills the active window) because I was not able to stop the recording. Is there a way to fix that?
Combobox code:
private void Handlebox_SelectedIndexChanged(object sender, EventArgs e)
{
if (Handlebox.SelectedIndex == 0)
{
handle_name = Handlebox.Items[0].ToString();
}
else if (Handlebox.SelectedIndex == 1)
{
handle_name = Handlebox.Items[1].ToString();
}
else if (Handlebox.SelectedIndex == 2)
{
handle_name = Handlebox.Items[2].ToString();
}
else if (Handlebox.SelectedIndex == 3)
{
handle_name = Handlebox.Items[3].ToString();
}
else if (Handlebox.SelectedIndex == 4)
{
handle_name = Handlebox.Items[4].ToString();
}
else if (Handlebox.SelectedIndex == 5)
{
handle_name = Handlebox.Items[5].ToString();
}
}
The way I add all the processes in the combobox:
foreach (Process process in processlist)
{
if (!String.IsNullOrEmpty(process.MainWindowTitle))
{
Handlebox.Items.Add(process.ProcessName);
}
}
The script that the whole recording takes place:
// Record video:
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
public Bitmap CaptureApplication(string procName)
{
Process proc;
// Cater for cases when the process can't be located.
try
{
proc = Process.GetProcessesByName(procName)[0];
}
catch (IndexOutOfRangeException e)
{
return null;
}
// You need to focus on the application
SetForegroundWindow(proc.MainWindowHandle);
ShowWindow(proc.MainWindowHandle, SW_RESTORE);
// Delay
Thread.Sleep(1000);
Rect rect = new Rect();
IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
// sometimes it gives error.
while (error == (IntPtr)0)
{
error = GetWindowRect(proc.MainWindowHandle, ref rect);
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(rect.left,rect.top,0,0,new Size(width, height),CopyPixelOperation.SourceCopy);
return bmp;
}
public void RecordVideo()
{
// Keep track of time:
watch.Start();
// Save screenshot:
string name = tempPath + "//screenshot-" + fileCount + ".png";
CaptureApplication(handle_name).Save(name, ImageFormat.Png);
inputImageSequence.Add(name);
fileCount++;
// Dispose of bitmap:
CaptureApplication(handle_name).Dispose();
}
Apologies for the brevity and style of this reply; I'm currently on mobile.
Could you express your combobox event handler as:
private void Handlebox_SelectedIndexChanged(object sender, EventArgs e)
{
if (Handlebox.SelectedIndex == -1) return;
handle_name = Handlebox.SelectedItem.ToString();
}
I'm not sure what exactly you want this application to do eventually, but If all you want is to gracefully exit, I would look into registering an event handler that watches for a key combination, and stops and saves the recording when it is pressed. I can't easily type out how to do that, but some concerted googling should help you.
I have made a recorder program that takes screenshots and, in the end, combines them into an mp4 video. The program now works by capturing the whole screen. What I need to do is to allow the user to select a window to record from. Is there a way to use the handle to record only that window?
Forms1.cs[Design]
The Recorder script is bellow
class Recorder
{
public static int userwidth = 1;
public static int userhight = 1;
public static int v=0;
//Video variables:
public static Rectangle bounds;
private string outputPath = "";
private string tempPath = "";
private int fileCount = 1;
private List<string> inputImageSequence = new List<string>();
//File variables:
private string audioName = "mic.wav";
private string videoName = "video.mp4";
private string finalName = "FinalVideo.mp4-";
//Time variable:
Stopwatch watch = new Stopwatch();
//Audio variables:
public static class NativeMethods
{
[DllImport("winmm.dll", EntryPoint = "mciSendStringA", ExactSpelling = true, CharSet = CharSet.Ansi, SetLastError = true)]
public static extern int record(string lpstrCommand, string lpstrReturnString, int uReturnLength, int hwndCallback);
}
//ScreenRecorder Object:
public ScreenRecorder(Rectangle b, string outPath)
{
//Create temporary folder for screenshots:
CreateTempFolder("tempScreenCaps");
//Set variables:
bounds = b;
outputPath = outPath;
}
//Create temporary folder:
private void CreateTempFolder(string name)
{
//Check if a C or D drive exists:
if (Directory.Exists("D://"))
{
string pathName = $"D://{name}";
Directory.CreateDirectory(pathName);
tempPath = pathName;
}
else
{
string pathName = $"C://Documents//{name}";
Directory.CreateDirectory(pathName);
tempPath = pathName;
}
}
//Change final video name:
public void setVideoName(string name)
{
finalName = name;
}
//Delete all files and directory:
private void DeletePath(string targetDir)
{
string[] files = Directory.GetFiles(targetDir);
string[] dirs = Directory.GetDirectories(targetDir);
//Delete each file:
foreach (string file in files)
{
File.SetAttributes(file, FileAttributes.Normal);
File.Delete(file);
}
//Delete the path:
foreach (string dir in dirs)
{
DeletePath(dir);
}
Directory.Delete(targetDir, false);
}
//Delete all files except the one specified:
private void DeleteFilesExcept(string targetDir, string excDir)
{
string[] files = Directory.GetFiles(targetDir);
//Delete each file except specified:
foreach (string file in files)
{
if (file != excDir)
{
File.SetAttributes(file, FileAttributes.Normal);
File.Delete(file);
}
}
}
//Clean up program on crash:
public void cleanUp()
{
if (Directory.Exists(tempPath))
{
DeletePath(tempPath);
}
}
//Return elapsed time:
public string getElapsed()
{
return string.Format("{0:D2}:{1:D2}:{2:D2}", watch.Elapsed.Hours, watch.Elapsed.Minutes, watch.Elapsed.Seconds);
}
//Record video:
public void RecordVideo()
{
//Keep track of time:
watch.Start();
using (Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height))
{
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
}
//Save screenshot:
string name = tempPath + "//screenshot-" + fileCount + ".png";
bitmap.Save(name, ImageFormat.Png);
inputImageSequence.Add(name);
fileCount++;
//Dispose of bitmap:
bitmap.Dispose();
}
}
//Compare images
public static List<bool> GetHash(Bitmap bmpSource)
{
List<bool> lResult = new List<bool>();
//create new image with 16x16 pixel
Bitmap bmpMin = new Bitmap(bmpSource, new System.Drawing.Size(128,128));
for (int j = 0; j < bmpMin.Height; j++)
{
for (int i = 0; i < bmpMin.Width; i++)
{
//reduce colors to true / false
lResult.Add(bmpMin.GetPixel(i, j).GetBrightness() < 0.5f);
}
}
return lResult;
}
//Record audio:
public void RecordAudio()
{
NativeMethods.record("open new Type waveaudio Alias recsound", "", 0, 0);
NativeMethods.record("record recsound", "", 0, 0);
}
//Save audio file:
private void SaveAudio()
{
string audioPath = "save recsound " + outputPath + "//" + audioName;
NativeMethods.record(audioPath, "", 0, 0);
NativeMethods.record("close recsound", "", 0, 0);
}
//Save video file:
public void SaveVideo(int width, int height, int frameRate)
{
for(int k=1; k > fileCount; k++)
{
List<bool> iHash1 = GetHash(new Bitmap(tempPath + "//screenshot-" + k + ".png"));
List<bool> iHash2 = GetHash(new Bitmap(tempPath + "//screenshot-" + (k + 1) + ".png"));
//determine the number of equal pixel (x of256)
long equalElements = iHash1.Zip(iHash2, (i, j) => i == j).Count(eq => eq);
if (equalElements > 16380)
{
var filePath = tempPath + "//screenshot-" + k + ".png";
File.Delete(filePath);
}
}
using (VideoFileWriter vFWriter = new VideoFileWriter())
{
vFWriter.Open(outputPath + "//"+ videoName, width, height, frameRate, VideoCodec.MPEG4 );
//Make each screenshot into a video frame:
foreach (string imageLocation in inputImageSequence)
{
Bitmap imageFrame = System.Drawing.Image.FromFile(imageLocation) as Bitmap;
vFWriter.WriteVideoFrame(imageFrame);
imageFrame.Dispose();
}
//Close:
vFWriter.Close();
}
}
//Combine video and audio files:
private void CombineVideoAndAudio(string video, string audio)
{
//FFMPEG command to combine video and audio:
string args = $"/c ffmpeg -i \"{video}\" -i \"{audio}\" -shortest {finalName}";
ProcessStartInfo startInfo = new ProcessStartInfo
{
CreateNoWindow = false,
FileName = "cmd.exe",
WorkingDirectory = outputPath,
Arguments = args
};
//Execute command:
using (Process exeProcess = Process.Start(startInfo))
{
exeProcess.WaitForExit();
}
}
public void Stop()
{
//Stop watch:
watch.Stop();
//Video variables:
int width = bounds.Width;
int height = bounds.Height;
int frameRate =v+10;
//Save audio:
SaveAudio();
//Save video:
SaveVideo(width, height, frameRate);
//Combine audio and video files:
CombineVideoAndAudio(videoName, audioName);
//Delete the screenshots and temporary folder:
DeletePath(tempPath);
//Delete separated video and audio files:
DeleteFilesExcept(outputPath, outputPath + "\\" + finalName);
}
}
As Ryan said in the comments, if you see the Get a screenshot of a specific application the code that actually works is the one below:
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hWnd);
private const int SW_RESTORE = 9;
[DllImport("user32.dll")]
private static extern IntPtr ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll")]
public static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
public Bitmap CaptureApplication(string procName)
{
Process proc;
// Cater for cases when the process can't be located.
try
{
proc = Process.GetProcessesByName(procName)[0];
}
catch (IndexOutOfRangeException e)
{
return null;
}
// You need to focus on the application
SetForegroundWindow(proc.MainWindowHandle);
ShowWindow(proc.MainWindowHandle, SW_RESTORE);
// You need some amount of delay, but 1 second may be overkill
Thread.Sleep(1000);
Rect rect = new Rect();
IntPtr error = GetWindowRect(proc.MainWindowHandle, ref rect);
// sometimes it gives error.
while (error == (IntPtr)0)
{
error = GetWindowRect(proc.MainWindowHandle, ref rect);
}
int width = rect.right - rect.left;
int height = rect.bottom - rect.top;
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(rect.left,rect.top,0,0,new Size(width, height),CopyPixelOperation.SourceCopy);
return bmp;
}
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 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
I have a working wrapper class for Dallmeier camera devices, It contains a callback method to receive the current YUV image.
See details C# wrapper for array of three pointers.
I have a button on my form that gets the YUV Image. The callback returns 'yuvData' which is an array of three pointers to Y, U, and V part of image.
I then copy the three pointers into thier own pointer and then copy them into a byte array. The yuvCallback continues to run until I disconnect the camera.
Am I using Marshal.Copy correctly?
public class DLMSDK
{
public delegate int YUVDataCallback(dlm_yuvdataParametersStructure pParameters);
DllImport(#"DallmeiersDLL\davidapileolive.dll")]
public extern static int dlm_setYUVDataCallback(int SessionHandle, YUVDataCallback dataCallback);
[StructLayout(LayoutKind.Explicit, Size = 32)]
public struct dlm_yuvdataParametersStructure
{
[FieldOffset(0)]
public int IPlayerID;
[FieldOffset(4), MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
public IntPtr[] yuvData;
[FieldOffset(8), MarshalAs(UnmanagedType.ByValArray, SizeConst=1)]
public IntPtr[] pitch;
[FieldOffset(12)]
public int width;
[FieldOffset(16)]
public int height;
[FieldOffset(18)]
public long ts;
[FieldOffset(28), MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
public IntPtr[] extData;
}
}
public partical class Form1 : Form
{
int width; int height;
int yArraySize; int uvArraySize;
byte[] yBytes; byte[] uBytes; byte[] vBytes;
int sizeY; int sizeU; int sizeV;
IntPtr ptrY; IntPtr ptrU; IntPtr ptrV;
DLMSDK.YuvDataCallback yuvdataCallback;
private void Form1_Load(object send, EventArgs e)
{
error = DLMSDK.dlm_initSDK();
if (error == 0)
registerEvents();
}
private void registerEVents()
{
yuvdataCallback = yuvdataHandler;
}
private void btnGetYUV_Click(object sender, EventArgs e)
{
try
{
error = DLMSDK.dlm_setYUVDataCallback(SessionHandle, yuvdataCallback);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
public int yuvdataHandler(DLMSDK.dlm_yuvdataParametersStructure pParameters)
{
width = pParameters.width;
height = pParameters.height;
yArraySize = width * height;
uvArraySize = yArraySize/4;
yBytes = new byte[yArraySize];
uBytes = new byte[uvArraySize];
vBytes = new byte[uvArraySize];
sizeY = Marshal.SizeOf(yBytes[0]) * yBytes.Length;
ptrY = Marshal.AllocHGlobal(sizeY);
sizeU = Marshal.SizeOf(uBytes[0]) * uBytes.Length;
ptrU = Marshal.AllocHGlobal(sizeU);
sizeV = Marshal.SizeOf(vBytes[0]) * vBytes.Length;
ptrV = Marshal.AllocHGlobal(sizeV);
try
{
// Copy the three pointers to Y,U, & V pointers
Marshal.Copy(pParameters.yuvData, 0, ptrY, 1);
Marshal.Copy(pParameters.yuvData, 1, ptrU, 1);
Marshal.Copy(pParameters.yuvData, 2, ptrV, 1);
// Copy pointers to YUV byte arrays
Marshal.Copy(ptrY, yBytes, 0, sizeY);
Marshal.Copy(ptrU, uBytes, 0, sizeU);
Marshal.Copy(ptrV, vBytes, 0, sizeV);
// Convert Y (Luminance) to Greyscale and display
Bitmap bmp = ImgConvert.ToGreyscale(yBytes, width, height);
DisplayImage(bmp);
}
finally
{
if (ptrY != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrY);
ptrY = IntPtr.Zero;
}
if (ptrU != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrU);
ptrU = IntPtr.Zero;
}
if (ptrV != IntPtr.Zero)
{
Marshal.FreeHGlobal(ptrV);
ptrV = IntPtr.Zero;
}
}
return 0;
}
}
I see you posted a similar question over at the MSDN forums: http://social.msdn.microsoft.com/Forums/en-US/clr/thread/67d20c16-d58b-444d-9689-88fab2792ab1
As I wrote over there, it's not correct to pack the callback delegate's parameters into a structure and pass it by value. So the first step is to correct the delegate signature.