C# con doble buffer
Explicación del doble buffer en C#:
En pocas palabras, cuando realizamos una operación de dibujo, el sistema no presenta directamente el contenido en la pantalla, sino que primero lo guarda en el memoria y luego genere los resultados de una vez. Si no utiliza el almacenamiento en búfer doble, encontrará que la pantalla parpadeará violentamente durante el proceso de dibujo porque el fondo se actualiza constantemente. Esto no sucederá si espera a que el usuario lo haga. termine de dibujar antes de generar, el método específico es en realidad crear primero un objeto de mapa de bits, luego guardar el contenido en él y finalmente presentar la imagen.
El problema del doble buffer de GDI
Un malentendido de larga data: existe una diferencia entre .net1.1 y .net 2.0 en el manejo del doble buffer de los controles.
En .net 1.1, use: this.SetStyle(ControlStyles.DoubleBuffer, true);
En .net 2.0, use: this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
Análisis de las causas clave del parpadeo de la pantalla:
1 Cuando la ventana de dibujo se vuelve a dibujar debido a cambios de tamaño, posición y estado.
Cada vez que el El contenido o el tamaño de la ventana de dibujo cambia una vez, se debe llamar al evento Paint para realizar una operación de redibujo, lo que hará que la pantalla se actualice nuevamente para mantener la visualización normal de la ventana. El proceso de actualización hará que se vuelvan a dibujar todos los elementos gráficos, y la operación de volver a dibujar de cada elemento gráfico no provocará que se produzca el evento Paint, por lo que cada actualización de la ventana solo llamará al evento Paint una vez. Durante el proceso de actualización de la ventana, el rediseño de cada elemento gráfico se mostrará inmediatamente en la ventana. Por lo tanto, siempre que la posición del elemento gráfico esté ubicada en toda la ventana, se actualizará. Sin embargo, el tiempo de actualización es diferente. , y el fenómeno de parpadeo aparecerá naturalmente.
Entonces, el factor clave que hace que la ventana parpadee en este momento no es el número de llamadas al evento Paint, sino el redibujado de cada elemento gráfico.
Según el análisis anterior, se puede ver que cuando la cantidad de elementos gráficos no es grande, no hay muchas posiciones de actualización de la ventana y el efecto de parpadeo de la ventana no es grave; Los elementos gráficos son grandes, la cantidad de elementos gráficos para volver a dibujar en la ventana de dibujo aumenta, cada actualización de la ventana de dibujo hará que se vuelvan a dibujar más elementos gráficos, se actualizarán más posiciones en la ventana y el fenómeno de parpadeo naturalmente será cada vez mayor. más serio. Especialmente cuando los elementos gráficos son más grandes y el tiempo de dibujo es más largo, el problema de parpadeo será más grave porque el retraso será mayor.
La clave para resolver el problema anterior es permitir que todos los elementos gráficos se muestren en la ventana al mismo tiempo durante la actualización de la ventana.
2. Al realizar operaciones de dibujo con seguimiento del mouse o deformar primitivas.
Al realizar operaciones de dibujo con seguimiento del mouse o deformar primitivas, los eventos de Paint aumentarán en gran medida la cantidad de actualizaciones de la ventana. . Aunque todos los elementos gráficos se muestran en la ventana al mismo tiempo durante una actualización de la ventana, también habrá un retraso de tiempo, porque el intervalo de tiempo entre las actualizaciones de la ventana es mucho más corto que el tiempo que tardan los elementos gráficos en mostrarse en la ventana. siempre. ¡Por lo tanto, el fenómeno del parpadeo no se puede eliminar por completo!
Entonces, el factor clave que hace que la ventana parpadee en este momento es la cantidad de veces que ocurre el evento Paint.
La clave para resolver este problema es establecer varias propiedades clave del formulario o control.
Utilice el almacenamiento en búfer doble
Tecnologías clave para resolver el almacenamiento en búfer doble:
1. Establezca varias propiedades del control del elemento de visualización: debe configurarse; de lo contrario, el efecto desaparecerá. no ser ¡Es obvio!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
2. Durante el proceso de actualización, todos los elementos gráficos se muestran en la ventana al mismo tiempo.
Esto se puede lograr de las siguientes maneras, todas las cuales implican la creación de objetos Gráficos.
Implementación específica
1. Usar doble buffer predeterminado
(1) La forma más fácil de usar doble buffer en una aplicación es usar .NET Framework para Doble buffer predeterminado proporcionado por formularios y controles. Estableciendo la propiedad DoubleBuffered en verdadero.
this.DoubleBuffered=true;
(2) Utilice el método SetStyle para habilitar el almacenamiento en búfer doble predeterminado para Windows Forms y controles de Windows creados.
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
2. Establecer doble buffer manualmente
.netframework proporciona una clase BufferedGraphicsContext responsable de asignar y administrar por separado los buffers de gráficos. distrito. Cada dominio de aplicación tiene su propia instancia predeterminada de BufferedGraphicsContext que administra todo el almacenamiento en búfer doble predeterminado para esta aplicación. En la mayoría de los casos, solo hay un dominio de aplicación por aplicación, por lo que generalmente solo hay un BufferedGraphicsContext predeterminado por aplicación. La instancia predeterminada de BufferedGraphicsContext es administrada por la clase BufferedGraphicsManager. Los pasos para implementar el doble almacenamiento en búfer mediante la gestión de BufferedGraphicsContext son los siguientes:
(1) Obtener una referencia a una instancia de la clase BufferedGraphicsContext.
(2) Cree una instancia de la clase BufferedGraphics llamando al método BufferedGraphicsContext.Allocate.
(3) Dibuje gráficos en el búfer de gráficos configurando la propiedad BufferedGraphics.Graphics.
(4) Cuando se completan todas las operaciones de dibujo en el búfer de gráficos, se puede llamar al método BufferedGraphics.Render para representar el contenido del búfer en la superficie de dibujo asociada con el búfer o en la superficie de dibujo especificada.
(5) Después de completar la representación de gráficos, llame al método Dispose para liberar recursos del sistema en la instancia de BufferedGraphics.
Ejemplo completo, dibuja 10.000 pequeños círculos generados aleatoriamente en un marco rectangular de 400*400.
BufferedGraphicsContext actual = BufferedGraphicsManager.Current; //(1)
BufferedGraphics bg;
bg = current.Allocate(this.CreateGraphics(), this. DisplayRectangle); //(2)
Gráficos g = bg.Graphics; //(3)
//Ancho aleatorio 400 y alto 400
Sistema rnd aleatorio = new Random();
int x, y, w, h, r, i;
for (i = 0; i lt; 10000; i)<. / p>
{
x = rnd.Siguiente(400);
y = rnd.Siguiente(400);
r = rnd. Siguiente (20);
w = rnd.Next(10);
h = rnd.Next(10);
g.DrawEllipse(Pens. Azul, x, y, w, h);
}
bg.Render();//(4)
//bg.Render( this .CreateGraphics());
bg.Dispose();//(5)
3. Abra un búfer usted mismo (como un objeto de mapa de bits que no se muestra) , en el que una vez completado el dibujo, se mostrará nuevamente.
El código completo es el siguiente:
Bitmap bt = new Bitmap(400, 400);
Graphics bg = Graphics.FromImage(bt);
System.Random rnd = new Random();
int x, y, w, h, r, i;
for (i = 0; i lt; 10000 ; i )
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
bg. DrawEllipse(Pens.Blue, x, y, w, h);
}
this.CreateGraphics().DrawImage(bt, nuevo punto(0, 0) );
Otro ejemplo, casi el mismo
Cómo crear un objeto Graphics:
Crea un lienzo con el mismo tamaño que el control de visualización en. en la memoria. En este lienzo Crear objeto de gráficos.
Todos los elementos gráficos se dibujan en este lienzo. Una vez completado el dibujo, el lienzo se utiliza para cubrir el fondo del control de visualización, logrando así el efecto de "mostrar una vez y actualizar solo una vez".
Código de implementación (en el método OnPaint):
Rectangle rect = e.ClipRectangle;
Bitmap bufferimage = new Bitmap(this.Width, this.Height )
Gráficos g = Graphics.FromImage(bufferimage);
g.Clear(this.BackColor
g.SmoothingMode = SmoothingMode.HighQuality; /Alta calidad
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //Alta calidad de desplazamiento de píxeles
foreach (IShape drawobject en doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject .TrackerState == config.Module.Core.TrackerState.Selected
amp;amp; this.CurrentOperator == Enum.Operator.Transfrom)//Muestra puntos de acceso primitivos solo al editar operaciones de nodo
{
drawobject.DrawTracker(g);
}
}
}
usando (Gráficos tg = e.Graphics)
{
tg.DrawImage(bufferimage, 0, 0); //Pega el lienzo en la pantalla
}
b. Crear objetos gráficos directamente en la memoria:
Rectangle rect = e.ClipRectangle;
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);
Gráficos g = myBuffer.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
p>
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.Clear(this.BackColor);
foreach (objeto de dibujo IShape en doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
<p>{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
amp;amp ; this.CurrentOperator == Enum.Operator.Transfrom)//Muestra puntos de acceso primitivos solo al editar operaciones de nodo
{
drawobject.DrawTracker(g);
}
}
}
myBuffer.Render(e.Graphics);
g.Dispose();
myBuffer.Dispose(); //Liberar recursos
En este punto, el problema del doble almacenamiento en búfer está resuelto. Los dos métodos tienen el mismo efecto, pero el último método ocupa muy poca memoria. ¡No habrá pérdidas de memoria!
El siguiente paso es implementar el efecto de arrastre de imagen de acdsee. Al principio no entendí el doble búfer y pensé que el doble búfer podría resolver este problema. Sin embargo, descubrí que el uso del doble búfer no tenía ningún efecto. Le pedí consejo a un experto y luego modifiqué algunos códigos para lograr este efecto.
La imagen está en PictureBox1.
Mapa de bits actualMap;
bool first = true;
imagen vacía privadaBox1_MouseDown(remitente del objeto, MouseEventArgs e)
{
if (zoom == 0)
{
if (e.Button == MouseButtons.Left) //arrastrando
mousedrag = e .Ubicación;
Imagen myImage = myMap.GetMap();
currentMap = new Bitmap(myImage);
primero = false;
}
}
imagen vacía privadaBox1_MouseMove(remitente del objeto, MouseEventArgs e)
{
if (zoom == 0amp; amp;!first)
{
Imagen img = nuevo mapa de bits (Tamaño.Ancho, Tamaño.Alto);
Gráficos g = Gráficos.FromImage(img
g.Clear(Color.Transparent); //El color de fondo que se muestra después de mover la imagen
g.SmoothingMode = SmoothingMode.HighQuality //Alta calidad; p>
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //Alta calidad de desplazamiento de píxeles
g.DrawImageUnscaled(currentMap, new System.Drawing.Point(e.Location.X - mousedrag.X, e . Location.Y - mousedrag.Y)); // Mueve la imagen en g. La imagen original se dibuja en (0, 0), así que usa directamente new System.Drawing.Point (e.Location.X - mousedrag.X). , e. Location.Y - mousedrag.Y) está bien.
g.Dispose();
pictureBox1.Image = img; //img es una imagen temporal que se genera después de moverla en la posición del mouse
}
}
imagen vacía privadaBox1_MouseUp(objeto remitente, MouseEventArgs e)
{
if (zoom == 0) p>
{
System.Drawing.Point pnt = nuevo System.Drawing.Point(Ancho / 2 (mousedrag.X - e.Location.X),
Altura / 2 (mousedrag.Y - e.Location.Y));
myMap.Center = myMap.ImageToWorld(pnt);
pictureBox1.Image = myMap.GetMap( );
first = true;
}
}
Hable sobre la idea, cree un mapa de bits, currentMap cuando esté el mouse. Al hacer clic, use Contiene la imagen actual. Cuando se mueve el mouse, la imagen se dibuja de acuerdo con la posición del mouse. Finalmente, cuando el mouse está arriba, la imagen se vuelve a dibujar.