【发布时间】:2021-03-12 17:48:18
【问题描述】:
我正在尝试制作屏幕共享应用程序。作为第一步,我尝试使用屏幕截图共享主机的屏幕。 为了将主机与客户端连接,我使用 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);
}
最后,我在新窗口中使用这两个属性从字符串创建一个 Bitmap 对象,并在该对象上使用转换器,以便我可以将 XAML 的 Image 控件绑定到它:
private Bitmap _image;
public Bitmap Image
{
get => _image;
set
{
_image = value;
NotifyPropertyChanged();
Image.Save(@"C:\Users\roudy\Desktop\test.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); }
}
}
我在属性的设置器中添加了 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:\Users\roudy\Desktop\SaveIcon.png");
}
注意:当我尝试从“ImageData”属性中的文件加载位图时,它也不起作用,所以它似乎是一个线程问题,当它从父窗口更改时(通过 SignalR 处理程序)它没有不显示。
【问题讨论】:
-
添加显示从文件加载位图时您所做的代码,以便社区可以比较实现。
-
这完全令人困惑。您最终在哪里从 base64 字符串加载 BitmapImage?当您在从 base64 字符串转换的字节数组上获得 MemoryStream 时,您可以直接将其传递给 BitmapImage 的 StreamSource 属性。
-
@Clemens 很抱歉,如果不清楚,这是我在这里的第一篇文章。我正在使用 byte[]->MemoryStream-> Bitmap 将字符串转换为位图,然后使用转换器将图像的源绑定到位图。我使用了来自“gdi32.dll”的转换器
-
@Clemens 我想我已经尝试过你的方法,老实说,我已经尝试了几十种方法,但都没有奏效。无论如何,谢谢你指出,我明天早上再试一次,看看是否有帮助。