实现新的图像更新逻辑:添加LastImage和InfoImage处理
This commit is contained in:
@@ -19,7 +19,25 @@ namespace JoyD.Windows.CS.Toprie
|
||||
// 是否正在接收图像
|
||||
private bool _isReceivingImage = false;
|
||||
|
||||
|
||||
// 全局图像缓冲区bitmap,用于在其上绘制图像和mask信息
|
||||
private Bitmap _imageBuffer = null;
|
||||
private const int BUFFER_WIDTH = 512;
|
||||
private const int BUFFER_HEIGHT = 384;
|
||||
|
||||
// 最后接收的图像
|
||||
private Image _lastImage = null;
|
||||
|
||||
// 信息图像,用于显示额外信息
|
||||
private Image _infoImage = null;
|
||||
|
||||
// 用于保护_lastImage的线程锁
|
||||
private readonly object _lastImageLock = new object();
|
||||
|
||||
// 用于保护_infoImage的线程锁
|
||||
private readonly object _infoImageLock = new object();
|
||||
|
||||
// 是否显示信息图像
|
||||
private bool _isDisplayingInfo = false;
|
||||
|
||||
// 项目路径,用于数据文件的存取
|
||||
private string _projectPath = "";
|
||||
@@ -43,10 +61,10 @@ namespace JoyD.Windows.CS.Toprie
|
||||
if (_deviceManager != null)
|
||||
{
|
||||
_deviceManager.ProjectPath = _projectPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 显示错误的定时器
|
||||
private System.Windows.Forms.Timer _errorDisplayTimer;
|
||||
@@ -93,8 +111,8 @@ namespace JoyD.Windows.CS.Toprie
|
||||
{
|
||||
Console.WriteLine($"处理暂停/恢复图像更新时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Camera()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -105,6 +123,9 @@ namespace JoyD.Windows.CS.Toprie
|
||||
// 将设计模式状态传递给DeviceManager
|
||||
UpdateDesignModeStatus();
|
||||
|
||||
// 初始化图像缓冲区
|
||||
InitializeImageBuffer();
|
||||
|
||||
// 只有在非设计模式下才初始化设备管理器和错误定时器
|
||||
if (!DesignMode)
|
||||
{
|
||||
@@ -180,7 +201,32 @@ namespace JoyD.Windows.CS.Toprie
|
||||
_deviceManager.ConnectionException += DeviceManager_ConnectionException;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 初始化图像缓冲区
|
||||
/// </summary>
|
||||
private void InitializeImageBuffer()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建512*384大小的bitmap作为图像缓冲区
|
||||
_imageBuffer = new Bitmap(BUFFER_WIDTH, BUFFER_HEIGHT);
|
||||
Console.WriteLine($"图像缓冲区已初始化: {BUFFER_WIDTH}x{BUFFER_HEIGHT}");
|
||||
|
||||
// 初始化缓冲区为黑色背景
|
||||
using (Graphics g = Graphics.FromImage(_imageBuffer))
|
||||
{
|
||||
g.Clear(Color.Black);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"初始化图像缓冲区失败: {ex.Message}");
|
||||
// 如果初始化失败,确保_imageBuffer为null
|
||||
_imageBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化错误显示定时器
|
||||
/// </summary>
|
||||
@@ -329,7 +375,7 @@ namespace JoyD.Windows.CS.Toprie
|
||||
if (pauseImageUpdateToolStripMenuItem.Text == "恢复图像更新")
|
||||
return;
|
||||
if (DesignMode) return;
|
||||
Image image = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (e.ImageData != null && e.ImageData.Length > 0)
|
||||
@@ -344,306 +390,253 @@ namespace JoyD.Windows.CS.Toprie
|
||||
using (Image tempImage = System.Drawing.Image.FromStream(ms))
|
||||
{
|
||||
// 创建一个全新的位图而不仅仅是克隆,确保数据完整性
|
||||
image = new Bitmap(tempImage);
|
||||
Image newImage = new Bitmap(tempImage);
|
||||
|
||||
// 立即验证新创建的图像是否有效
|
||||
try
|
||||
{
|
||||
// 访问Width和Height属性来验证图像是否有效
|
||||
int width = image.Width;
|
||||
int height = image.Height;
|
||||
int width = newImage.Width;
|
||||
int height = newImage.Height;
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
Console.WriteLine("创建的图像尺寸无效");
|
||||
image.Dispose();
|
||||
image = null;
|
||||
newImage.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Console.WriteLine("创建的图像无效");
|
||||
image.Dispose();
|
||||
image = null;
|
||||
newImage.Dispose();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"创建图像失败: {ex.Message}");
|
||||
// 确保在异常情况下释放资源
|
||||
if (image != null)
|
||||
{
|
||||
image.Dispose();
|
||||
image = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
{
|
||||
Console.WriteLine("接收到空或无效的图像数据");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试创建图像的克隆以传递给UI线程
|
||||
Image clonedImage = null;
|
||||
try
|
||||
{
|
||||
clonedImage = (Image)image.Clone();
|
||||
|
||||
// 验证克隆的图像是否有效
|
||||
if (clonedImage.Width <= 0 || clonedImage.Height <= 0)
|
||||
{
|
||||
Console.WriteLine("克隆的图像尺寸无效");
|
||||
clonedImage.Dispose();
|
||||
image.Dispose(); // 确保原始图像也被释放
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"克隆图像失败: {ex.Message}");
|
||||
image.Dispose(); // 确保原始图像被释放
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保在UI线程上更新,并且检查控件是否已被释放
|
||||
if (!this.IsDisposed && !this.Disposing)
|
||||
{
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建本地副本以避免闭包问题
|
||||
Image imageForUI = clonedImage;
|
||||
clonedImage = null; // 防止在异步操作期间被修改
|
||||
|
||||
// 使用BeginInvoke在UI线程上更新图像,避免可能的死锁问题
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
|
||||
// 按照用户要求:收到图像数据后,将图像保存到LastImage中,调用更新
|
||||
lock (_lastImageLock)
|
||||
{
|
||||
UpdateImageOnUI(imageForUI);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"更新UI图像失败: {ex.Message}");
|
||||
// 如果UI更新失败,确保克隆的图像被释放
|
||||
if (imageForUI != null)
|
||||
// 释放旧的LastImage资源
|
||||
if (_lastImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
imageForUI.Dispose();
|
||||
}
|
||||
catch {}
|
||||
try { _lastImage.Dispose(); } catch {}
|
||||
}
|
||||
// 设置新的LastImage
|
||||
_lastImage = newImage;
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 异常情况下确保释放图像资源
|
||||
if (clonedImage != null)
|
||||
{
|
||||
try
|
||||
|
||||
// 调用更新UI
|
||||
this.BeginInvoke(new Action(() =>
|
||||
{
|
||||
clonedImage.Dispose();
|
||||
}
|
||||
catch {}
|
||||
clonedImage = null;
|
||||
try
|
||||
{
|
||||
UpdateImageOnUI();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"更新UI图像失败: {ex.Message}");
|
||||
}
|
||||
}));
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 在UI线程上直接更新图像
|
||||
UpdateImageOnUI(clonedImage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果控件已释放,确保释放图像资源
|
||||
clonedImage.Dispose();
|
||||
}
|
||||
|
||||
// 释放原始图像资源
|
||||
image.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"处理图像接收事件失败: {ex.Message}");
|
||||
// 确保在任何异常情况下都释放原始图像
|
||||
if (image != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
image.Dispose();
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
Console.WriteLine($"处理接收到的图像时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在UI线程上更新图像
|
||||
/// </summary>
|
||||
private void UpdateImageOnUI(Image image)
|
||||
{
|
||||
private void UpdateImageOnUI(Image image) // 保留原有方法签名,处理旧调用
|
||||
{
|
||||
UpdateImageOnUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在UI线程上更新图像 - 新实现,按照用户要求:
|
||||
/// 1. 先将LastImage绘制到全局缓冲
|
||||
/// 2. 再将InfoImage绘制到缓冲
|
||||
/// 3. 最后一次性绘制到图像框的bitmap
|
||||
/// </summary>
|
||||
private void UpdateImageOnUI()
|
||||
{
|
||||
if (DesignMode) return;
|
||||
|
||||
// 线程安全检查 - 确保在UI线程上执行
|
||||
if (this.InvokeRequired)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
this.BeginInvoke(new Action<Image>(UpdateImageOnUI), image);
|
||||
{
|
||||
this.BeginInvoke(new Action(UpdateImageOnUI));
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
// 如果控件已被释放,确保释放图像资源
|
||||
if (image != null)
|
||||
{
|
||||
try { image.Dispose(); } catch {}
|
||||
}
|
||||
{
|
||||
Console.WriteLine("控件已释放,跳过UI更新");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 空值检查
|
||||
if (image == null)
|
||||
{
|
||||
Console.WriteLine("传入UpdateImageOnUI的图像为空");
|
||||
return;
|
||||
}
|
||||
// 连接状态检查 - 只在设备实际连接时处理图像
|
||||
if (_deviceManager.ConnectionStatus != ConnectionStatus.Connected)
|
||||
{
|
||||
Console.WriteLine("设备未连接,跳过图像更新");
|
||||
try { image.Dispose(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
// 增强图像有效性检查
|
||||
bool isImageValid = false;
|
||||
Image safeImage = null;
|
||||
Image lastImage = null;
|
||||
Image infoImage = null;
|
||||
Image oldImage = null;
|
||||
|
||||
try
|
||||
{
|
||||
// 创建一个安全的图像副本,确保原图像不被破坏
|
||||
safeImage = new Bitmap(image);
|
||||
{
|
||||
// 连接状态检查 - 只在设备实际连接时处理图像
|
||||
if (_deviceManager.ConnectionStatus != ConnectionStatus.Connected)
|
||||
{
|
||||
Console.WriteLine("设备未连接,跳过图像更新");
|
||||
return;
|
||||
}
|
||||
|
||||
// 预先验证图像是否有效
|
||||
if (safeImage.Width > 0 && safeImage.Height > 0)
|
||||
{
|
||||
// 尝试访问图像的像素数据,这是更严格的验证方式
|
||||
using (Graphics g = Graphics.FromImage(new Bitmap(1, 1)))
|
||||
{
|
||||
// 简单绘制操作验证图像是否可绘制
|
||||
g.DrawImage(safeImage, 0, 0, 1, 1);
|
||||
// 检查图像缓冲区是否有效
|
||||
if (_imageBuffer == null)
|
||||
{
|
||||
Console.WriteLine("图像缓冲区未初始化,尝试重新初始化");
|
||||
InitializeImageBuffer();
|
||||
if (_imageBuffer == null)
|
||||
{
|
||||
Console.WriteLine("重新初始化图像缓冲区失败");
|
||||
return;
|
||||
}
|
||||
isImageValid = true;
|
||||
Console.WriteLine($"图像验证成功: {safeImage.Width}x{safeImage.Height}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"图像尺寸无效: {safeImage.Width}x{safeImage.Height}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"图像验证失败: {ex.Message}");
|
||||
// 释放临时创建的安全图像
|
||||
if (safeImage != null)
|
||||
{
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
}
|
||||
// 释放原始图像
|
||||
try { image.Dispose(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// 更新图像前先检查控件是否存在/已释放
|
||||
if (this.IsDisposed || imageBox == null || imageBox.IsDisposed)
|
||||
{
|
||||
// 如果控件已释放,确保图像也被释放
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
try { image.Dispose(); } catch {}
|
||||
{
|
||||
Console.WriteLine("控件已释放,无法更新图像");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存旧图像引用,以便在设置新图像后释放
|
||||
Image oldImage = imageBox.Image;
|
||||
oldImage = imageBox.Image;
|
||||
|
||||
try
|
||||
{
|
||||
// 确保控件仍处于有效状态
|
||||
if (imageBox.IsDisposed || !isImageValid || safeImage == null)
|
||||
{
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
try { image.Dispose(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试设置新图像(使用验证过的安全副本)
|
||||
imageBox.Image = safeImage;
|
||||
|
||||
// 验证图像是否成功设置
|
||||
if (imageBox.Image != safeImage)
|
||||
{
|
||||
Console.WriteLine("图像设置失败,可能参数无效");
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
try { image.Dispose(); } catch {}
|
||||
return;
|
||||
}
|
||||
|
||||
// 释放原始图像,因为我们现在使用的是安全副本
|
||||
try { image.Dispose(); } catch {}
|
||||
|
||||
// 仅在新图像成功设置后释放旧图像
|
||||
if (oldImage != null && oldImage != safeImage) // 防止自引用释放
|
||||
{
|
||||
try
|
||||
{
|
||||
oldImage.Dispose();
|
||||
}
|
||||
catch {}
|
||||
// 获取当前的LastImage引用
|
||||
lock (_lastImageLock)
|
||||
{
|
||||
if (_lastImage != null)
|
||||
{
|
||||
lastImage = (Image)_lastImage.Clone();
|
||||
}
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message.Contains("参数无效"))
|
||||
{
|
||||
// 特别处理"参数无效"异常
|
||||
Console.WriteLine($"图像参数无效异常: {ex.Message}");
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
try { image.Dispose(); } catch {}
|
||||
|
||||
// 尝试设置旧图像回来,如果可能的话
|
||||
if (oldImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
imageBox.Image = oldImage;
|
||||
|
||||
// 获取当前的InfoImage引用
|
||||
lock (_infoImageLock)
|
||||
{
|
||||
if (_infoImage != null)
|
||||
{
|
||||
infoImage = (Image)_infoImage.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
// 在缓冲区上绘制图像 - 按照用户要求的步骤执行
|
||||
lock (_imageBuffer)
|
||||
{
|
||||
using (Graphics g = Graphics.FromImage(_imageBuffer))
|
||||
{
|
||||
// 清除缓冲区背景为黑色
|
||||
g.Clear(Color.Black);
|
||||
|
||||
// 步骤1:先将LastImage绘制到全局缓冲
|
||||
if (lastImage != null)
|
||||
{
|
||||
// 保持原始图像比例,居中显示
|
||||
float scale = Math.Min((float)BUFFER_WIDTH / lastImage.Width, (float)BUFFER_HEIGHT / lastImage.Height);
|
||||
int scaledWidth = (int)(lastImage.Width * scale);
|
||||
int scaledHeight = (int)(lastImage.Height * scale);
|
||||
int x = (BUFFER_WIDTH - scaledWidth) / 2;
|
||||
int y = (BUFFER_HEIGHT - scaledHeight) / 2;
|
||||
|
||||
g.DrawImage(lastImage, x, y, scaledWidth, scaledHeight);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果设置旧图像也失败,释放它
|
||||
try { oldImage.Dispose(); } catch {}
|
||||
|
||||
// 步骤2:再将InfoImage绘制到缓冲
|
||||
if (infoImage != null)
|
||||
{
|
||||
g.DrawImage(infoImage, 0, 0);
|
||||
}
|
||||
else if (_isDisplayingInfo)
|
||||
{
|
||||
// 如果没有InfoImage但需要显示信息,则绘制默认信息
|
||||
using (Font font = new Font("微软雅黑", 12, FontStyle.Bold))
|
||||
using (Brush textBrush = new SolidBrush(Color.Red))
|
||||
{
|
||||
g.DrawString("测试信息", font, textBrush, 10, 10);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要绘制暂停状态的mask信息
|
||||
bool isPaused = pauseImageUpdateToolStripMenuItem.Text == "恢复图像更新";
|
||||
if (isPaused)
|
||||
{
|
||||
// 绘制半透明黑色背景
|
||||
using (Brush brush = new SolidBrush(Color.FromArgb(100, Color.Black)))
|
||||
{
|
||||
g.FillRectangle(brush, 0, 0, BUFFER_WIDTH, BUFFER_HEIGHT);
|
||||
}
|
||||
|
||||
// 绘制"暂停"文字
|
||||
using (Font font = new Font("微软雅黑", 48, FontStyle.Bold))
|
||||
using (Brush textBrush = new SolidBrush(Color.White))
|
||||
{
|
||||
StringFormat format = new StringFormat();
|
||||
format.Alignment = StringAlignment.Center;
|
||||
format.LineAlignment = StringAlignment.Center;
|
||||
|
||||
g.DrawString("暂停", font, textBrush,
|
||||
new Rectangle(0, 0, BUFFER_WIDTH, BUFFER_HEIGHT), format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤3:创建缓冲区的副本用于显示,避免在显示时锁定缓冲区
|
||||
Bitmap displayImage = null;
|
||||
lock (_imageBuffer)
|
||||
{
|
||||
displayImage = (Bitmap)_imageBuffer.Clone();
|
||||
}
|
||||
|
||||
// 将全局缓冲一次性绘制到图像框的bitmap
|
||||
imageBox.Image = displayImage;
|
||||
|
||||
if (lastImage != null)
|
||||
{
|
||||
Console.WriteLine($"图像更新成功: {lastImage.Width}x{lastImage.Height}");
|
||||
}
|
||||
}
|
||||
catch (ArgumentException ex) when (ex.Message.Contains("参数无效"))
|
||||
{
|
||||
// 特别处理"参数无效"异常
|
||||
Console.WriteLine($"图像参数无效异常: {ex.Message}");
|
||||
|
||||
// 尝试设置旧图像回来,如果可能的话
|
||||
if (oldImage != null && !this.IsDisposed && imageBox != null && !imageBox.IsDisposed)
|
||||
{
|
||||
try
|
||||
{
|
||||
imageBox.Image = oldImage;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果设置旧图像也失败,释放它
|
||||
try { oldImage.Dispose(); } catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
{
|
||||
Console.WriteLine($"更新图像UI异常: {ex.Message}");
|
||||
// 确保在任何异常情况下都释放所有图像资源
|
||||
try { safeImage.Dispose(); } catch {}
|
||||
try { image.Dispose(); } catch {}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保在任何情况下都释放资源
|
||||
if (lastImage != null) { try { lastImage.Dispose(); } catch { } }
|
||||
if (infoImage != null) { try { infoImage.Dispose(); } catch { } }
|
||||
if (oldImage != null && oldImage != imageBox.Image) { try { oldImage.Dispose(); } catch { } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1383,11 +1376,62 @@ namespace JoyD.Windows.CS.Toprie
|
||||
|
||||
// 无论是否在设计模式下,都需要释放图像资源
|
||||
if (imageBox != null && !imageBox.IsDisposed && imageBox.Image != null)
|
||||
{
|
||||
{
|
||||
imageBox.Image.Dispose();
|
||||
imageBox.Image = null;
|
||||
}
|
||||
|
||||
// 释放图像缓冲区资源
|
||||
if (_imageBuffer != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_imageBuffer.Dispose();
|
||||
_imageBuffer = null;
|
||||
Console.WriteLine("图像缓冲区资源已释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"清理ImageBuffer资源异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 释放LastImage资源
|
||||
lock (_lastImageLock)
|
||||
{
|
||||
if (_lastImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastImage.Dispose();
|
||||
_lastImage = null;
|
||||
Console.WriteLine("LastImage资源已释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"清理LastImage资源异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 释放InfoImage资源
|
||||
lock (_infoImageLock)
|
||||
{
|
||||
if (_infoImage != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_infoImage.Dispose();
|
||||
_infoImage = null;
|
||||
Console.WriteLine("InfoImage资源已释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"清理InfoImage资源异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 释放组件资源
|
||||
if (components != null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user