我正在尝试制作一个屏幕共享应用程序。作为第一步,我尝试使用截图分享主机的屏幕。为了连接主机和客户端,我使用SignalR。身份验证完成后,我为主机启动了一个计时器,当它结束时将截取屏幕截图。截图代码如下:
public static byte[] TakeScreenshot(int width = 0, int height = 0)
{
var bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height,
PixelFormat.Format32bppArgb);
// Create a graphics object from the bitmap.
var gfxScreenshot = Graphics.FromImage(bmp);
// Take the screenshot from the upper left corner to the right bottom corner.
gfxScreenshot.CopyFromScreen(Screen.PrimaryScreen.Bounds.X,
Screen.PrimaryScreen.Bounds.Y,
0,
0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
if (width != 0 && height != 0)
bmp = new Bitmap(bmp, new Size(width, height));
using (var stream = new MemoryStream())
{
bmp.Save(stream, ImageFormat.Jpeg);
return stream.ToArray();
}
}
计时器经过的处理程序:
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var bytes = ImageHelper.TakeScreenshot();
string imageString = Convert.ToBase64String(bytes);
Communicator.Instance.Produce(new BroadcastDataComm() { DataType = DataType.Image, Data = imageString }, _connectedHost);
}
屏幕截图将被接收为字节数组,然后转换为Base64String,以便通过SignalR广播。当身份验证完成后,我正在创建一个新的WPF窗口(在父类Xaml.cs中)并显示它,然后,所有经过主机身份验证的客户端将接收生成的消息并将base64string发送到新窗口:
ScreenSharingWindow _window = new ScreenSharingWindow();
private void AuthenticateSuccess(string id)
{
ConnectionId = $"Connected to: {id}";
_connectedHost = id;
Dispatcher.Invoke(() => _window.Show());
_timer.Start();
}
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
最后,我创建了一个位图对象从字符串使用这两个属性在新窗口和使用转换器的对象,所以我可以绑定XAML的图像控制到它:
private Bitmap _image;
public Bitmap Image
{
get => _image;
set
{
_image = value;
NotifyPropertyChanged();
Image.Save(@"C:UsersroudyDesktoptest.jpeg", System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
private string _imageData;
public string ImageData
{
get => _imageData;
set
{
_imageData = value;
NotifyPropertyChanged();
var ms = new MemoryStream(Convert.FromBase64String(ImageData));
Image = new Bitmap(ms);
}
}
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Bitmap bmp)
return ImageSourceFromBitmap(bmp);
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
[DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeleteObject([In] IntPtr hObject);
public BitmapSource ImageSourceFromBitmap(Bitmap bmp)
{
var handle = bmp.GetHbitmap();
try
{
var ret = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
return ret;
}
finally { DeleteObject(handle); }
}
}
我在属性的setter中添加了image . save(…),以确保图像按预期接收并且正常工作,并且当我在桌面上检查它的缩略图时,我可以看到图像正确更新。但是由于某些原因,它拒绝在WPF控件中显示。我试着从文件中创建一个位图,它工作得很好,所以绑定和转换器是正确的。
为什么控件上的图像不显示?谢谢你。
编辑:为了检查绑定是否正确,我刚刚从新窗口的构造函数中创建了位图,并注释掉了父窗口设置属性的部分。父窗口中的新处理程序将是:
private void ScreenshotReceived(BroadcastDataComm ss)
{
if (!(ss.Data is string imgString))
return;
//Dispatcher.Invoke(()=> _window.ImageData = imgString);
}
和新窗口的构造函数:
public ScreenSharingWindow()
{
InitializeComponent();
DataContext = this;
Image = new Bitmap(@"C:UsersroudyDesktopSaveIcon.png");
}
注意:当我尝试从"ImageData"文件中加载位图时属性,它也不工作,所以它似乎是一个线程问题,当它从父窗口(通过SignalR处理程序)改变时,它不显示。
问题来自SignalR。我正在运行同一个项目的2个实例,屏幕截图被发送到主机而不是客户端,客户端需要它来创建一个新的窗口,这导致图像是空白的。感谢@Clemens使调试更容易,而不需要转换器和更多的复杂性。