在 Gtk# window 中刷新图像,一定时间后图像消失
Refreshing an image in a Gtk# window, after a certain amount of time the image disappears
我正在使用 C#、.Net 4.5 和 Gtk# 编写一个简单的应用程序,它捕获屏幕的一部分并将其加载到 window 中的图像上。几秒钟后图像消失,我收到以下错误:
Glib-CRITICAL **:尝试删除时未找到源 ID xxxx。
相关代码:
Program.cs:
using System.Windows.Forms;
namespace Dota2Trainer
{
class MainClass
{
public static void Main(string[] args)
{
Gtk.Application.Init();
MiniMapOverlay myWin = new MiniMapOverlay();
myWin.KeepAbove = true;
//myWin.Decorated = false;
myWin.Resize(200, 200);
myWin.ShowAll();
ScreenCapturer screenCap = new ScreenCapturer(30, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, myWin);
screenCap.Start();
Gtk.Application.Run();
}
}
}
MiniMapOverlay.cs:
using System;
using System.Drawing;
using System.IO;
using System.Timers;
using Gtk;
namespace Dota2Trainer
{
public partial class MiniMapOverlay : Gtk.Window
{
Gtk.Image image = null;
public MiniMapOverlay() : base(Gtk.WindowType.Toplevel)
{
image = new Gtk.Image();
var buffer = System.IO.File.ReadAllBytes(".\image0.jpeg");
var pixbuf = new Gdk.Pixbuf(buffer);
image.Pixbuf = pixbuf;
this.Add(image);
this.Build();
}
public void RefreshImage(byte[] buffer)
{
try
{
var pixbuf = new Gdk.Pixbuf(buffer);
image.Pixbuf = pixbuf;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
}
ScreenCapturer.cs:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Timers;
namespace Dota2Trainer
{
public delegate void OnSavedImage(byte[] screenCap);
public class ScreenCapturer
{
public System.Timers.Timer aTimer;
int interval= 30000;
readonly int screenWidth;
readonly int screenHeight;
Rectangle bounds;
const double miniMapWidthPercentage = 0.15;
const double miniMapHeightPercentage = 0.25;
Bitmap bitmap;
bool Busy = false;
ImageConverter converter = new ImageConverter();
public OnSavedImage onSaveImage;
public ScreenCapturer(int interval, int screenWidth, int screenHeight, MiniMapOverlay imageHolder)
{
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
this.interval = interval;
aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = this.interval;
int miniMapWidth = (int)(screenWidth * miniMapWidthPercentage);
int miniMapHeight = (int)(screenHeight * miniMapHeightPercentage);
int miniMapStartWidth = (screenWidth - miniMapWidth);
int miniMapStartHeight = (screenHeight - miniMapHeight);
bounds = new Rectangle(miniMapStartWidth, miniMapStartHeight, miniMapWidth, miniMapHeight);
bitmap = new Bitmap(bounds.Width, bounds.Height);
onSaveImage += imageHolder.RefreshImage;
}
public void Start()
{
aTimer.Enabled = true;
}
public void OnTimedEvent(object sender, ElapsedEventArgs e)
{
try
{
if (Busy)
return;
Busy = true;
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
onSaveImage((byte[])converter.ConvertTo(bitmap, typeof(byte[])));
}
Busy = false;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
}
完整的源代码可以在这里找到:
https://github.com/oldtimerza/Dota2Trainer
它的工作原理是简单地创建一个计时器,在一定时间后 运行s Screencapturer class 的 OnTimedEvent() 方法,它有一个委托将 运行 MiniMapOverlay RefreshImage() 方法。目前的代码令人震惊,但我只是想在重构之前获得一个粗略且准备好的版本并 运行ning。
我一直在寻找为什么会发生这种情况,有什么建议吗?
您应该使用 Glib.Timeout
而不是 System.Timers
。 System.Timers
可能存在线程问题,计时器事件发生在与 GTK 应用程序不同的线程上。 Glib.Timeout
以特定间隔在主 GTK 应用程序循环中对事件进行排队,并在同一线程中运行。但是,它不适合时间敏感的功能,因为应用程序循环正在处理其他事件,例如鼠标单击。我见过大约半秒到一秒的延迟,但那是在旧计算机上调试时的情况。这对您的应用程序来说似乎不是问题;仅供将来参考。此外,Glib.Timeout
将继续调用更新事件,直到您 return false。
uint timerId;
public void Start(uint interval)
{
timerId = GLib.Timeout.Add (interval, OnUpdateTimer);
}
protected bool OnUpdateTimer ()
{
try
{
if (Busy)
return;
Busy = true;
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
onSaveImage((byte[])converter.ConvertTo(bitmap, typeof(byte[])));
}
Busy = false;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
return true;
}
我正在使用 C#、.Net 4.5 和 Gtk# 编写一个简单的应用程序,它捕获屏幕的一部分并将其加载到 window 中的图像上。几秒钟后图像消失,我收到以下错误:
Glib-CRITICAL **:尝试删除时未找到源 ID xxxx。
相关代码:
Program.cs:
using System.Windows.Forms;
namespace Dota2Trainer
{
class MainClass
{
public static void Main(string[] args)
{
Gtk.Application.Init();
MiniMapOverlay myWin = new MiniMapOverlay();
myWin.KeepAbove = true;
//myWin.Decorated = false;
myWin.Resize(200, 200);
myWin.ShowAll();
ScreenCapturer screenCap = new ScreenCapturer(30, Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, myWin);
screenCap.Start();
Gtk.Application.Run();
}
}
}
MiniMapOverlay.cs:
using System;
using System.Drawing;
using System.IO;
using System.Timers;
using Gtk;
namespace Dota2Trainer
{
public partial class MiniMapOverlay : Gtk.Window
{
Gtk.Image image = null;
public MiniMapOverlay() : base(Gtk.WindowType.Toplevel)
{
image = new Gtk.Image();
var buffer = System.IO.File.ReadAllBytes(".\image0.jpeg");
var pixbuf = new Gdk.Pixbuf(buffer);
image.Pixbuf = pixbuf;
this.Add(image);
this.Build();
}
public void RefreshImage(byte[] buffer)
{
try
{
var pixbuf = new Gdk.Pixbuf(buffer);
image.Pixbuf = pixbuf;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
}
ScreenCapturer.cs:
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Timers;
namespace Dota2Trainer
{
public delegate void OnSavedImage(byte[] screenCap);
public class ScreenCapturer
{
public System.Timers.Timer aTimer;
int interval= 30000;
readonly int screenWidth;
readonly int screenHeight;
Rectangle bounds;
const double miniMapWidthPercentage = 0.15;
const double miniMapHeightPercentage = 0.25;
Bitmap bitmap;
bool Busy = false;
ImageConverter converter = new ImageConverter();
public OnSavedImage onSaveImage;
public ScreenCapturer(int interval, int screenWidth, int screenHeight, MiniMapOverlay imageHolder)
{
this.screenHeight = screenHeight;
this.screenWidth = screenWidth;
this.interval = interval;
aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = this.interval;
int miniMapWidth = (int)(screenWidth * miniMapWidthPercentage);
int miniMapHeight = (int)(screenHeight * miniMapHeightPercentage);
int miniMapStartWidth = (screenWidth - miniMapWidth);
int miniMapStartHeight = (screenHeight - miniMapHeight);
bounds = new Rectangle(miniMapStartWidth, miniMapStartHeight, miniMapWidth, miniMapHeight);
bitmap = new Bitmap(bounds.Width, bounds.Height);
onSaveImage += imageHolder.RefreshImage;
}
public void Start()
{
aTimer.Enabled = true;
}
public void OnTimedEvent(object sender, ElapsedEventArgs e)
{
try
{
if (Busy)
return;
Busy = true;
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
onSaveImage((byte[])converter.ConvertTo(bitmap, typeof(byte[])));
}
Busy = false;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
}
}
}
完整的源代码可以在这里找到:
https://github.com/oldtimerza/Dota2Trainer
它的工作原理是简单地创建一个计时器,在一定时间后 运行s Screencapturer class 的 OnTimedEvent() 方法,它有一个委托将 运行 MiniMapOverlay RefreshImage() 方法。目前的代码令人震惊,但我只是想在重构之前获得一个粗略且准备好的版本并 运行ning。
我一直在寻找为什么会发生这种情况,有什么建议吗?
您应该使用 Glib.Timeout
而不是 System.Timers
。 System.Timers
可能存在线程问题,计时器事件发生在与 GTK 应用程序不同的线程上。 Glib.Timeout
以特定间隔在主 GTK 应用程序循环中对事件进行排队,并在同一线程中运行。但是,它不适合时间敏感的功能,因为应用程序循环正在处理其他事件,例如鼠标单击。我见过大约半秒到一秒的延迟,但那是在旧计算机上调试时的情况。这对您的应用程序来说似乎不是问题;仅供将来参考。此外,Glib.Timeout
将继续调用更新事件,直到您 return false。
uint timerId;
public void Start(uint interval)
{
timerId = GLib.Timeout.Add (interval, OnUpdateTimer);
}
protected bool OnUpdateTimer ()
{
try
{
if (Busy)
return;
Busy = true;
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(new Point(bounds.Left, bounds.Top), Point.Empty, bounds.Size);
onSaveImage((byte[])converter.ConvertTo(bitmap, typeof(byte[])));
}
Busy = false;
}
catch (Exception exception)
{
Console.WriteLine(exception.Message);
}
return true;
}