可拖动的选择矩形

时间:2013-05-31 07:08:12

标签: c# .net winforms .net-3.5

在任何人指出之前我知道有一个问题具有相同的标题has already been asked here它只是不能回答我的问题。

在.NET 3.5中工作在那个问题中,我正在制作一个区域选择组件来选择图片上的区域。使用自定义控件显示图片,其中在OnPaint期间绘制图片。

我的选择矩形有以下代码:

internal class AreaSelection : Control
{
    private Rectangle selection
    {
        get { return new Rectangle(Point.Empty, Size.Subtract(this.Size, new Size(1, 1))); }
    }

    private Size mouseStartLocation;

    public AreaSelection()
    {
        this.Size = new Size(150, 150);
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.SupportsTransparentBackColor, true);
        this.BackColor = Color.FromArgb(70, 200, 200, 200);
    }

    protected override void OnMouseEnter(EventArgs e)
    {
        this.Cursor = Cursors.SizeAll;

        base.OnMouseEnter(e);
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        this.mouseStartLocation = new Size(e.Location);

        base.OnMouseDown(e);
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left)
        {
            Point offset = e.Location - this.mouseStartLocation;
            this.Left += offset.X;
            this.Top += offset.Y;
        }

        base.OnMouseMove(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.DrawRectangle(new Pen(Color.Black) { DashStyle = DashStyle.Dash }, this.selection);
        Debug.WriteLine("Selection redrawn");
    }
}

这给了我一个漂亮的半透明矩形,我可以拖动它。我遇到的问题是,拖动通过矩形显示的底层图像会滞后于矩形的位置。

移动矩形的速度越快,这就越明显。当我停止移动它时,图像会赶上并且一切都完美地对齐。 我认为矩形的绘制方式有问题,但我真的无法弄清楚它是什么...... 任何帮助将不胜感激。

编辑:

我注意到当我拖动选择区域时,观察者的重绘次数是选择区域的两倍。这可能是问题的原因吗?

编辑2:

以下是查看器的相关代码:

public enum ImageViewerViewMode
{
    Normal,
    PrintSelection,
    PrintPreview
}

public enum ImageViewerZoomMode
{
    None,
    OnClick,
    Lens
}

public partial class ImageViewer : UserControl
{
    /// <summary>
    /// The current zoom factor. Note: Use SetZoom() to set the value.
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public float ZoomFactor
    {
        get { return this.zoomFactor; }
        private set
        {
            this.zoomFactor = value;
        }
    }

    /// <summary>
    /// The maximum zoom factor to use
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public float MaximumZoomFactor
    {
        get
        {
            return this.maximumZoomFactor;
        }
        set
        {
            this.maximumZoomFactor = value;
            this.SetZoomFactorLimits();
        }
    }

    /// <summary>
    /// The minimum zoom factort to use
    /// </summary>
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public float MinimumZoomFactor
    {
        get
        {
            return this.minimumZoomFactor;
        }
        set
        {
            this.minimumZoomFactor = value;
            this.SetZoomFactorLimits();
        }
    }

    /// <summary>
    /// The multiplying factor to apply to each ZoomIn/ZoomOut command
    /// </summary>
    [Category("Behavior")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [DefaultValue(2F)]
    public float ZoomStep { get; set; }

    /// <summary>
    /// The image currently displayed by the control
    /// </summary>
    [Category("Data")]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public Image Image
    {
        get { return this.image; }
        set
        {
            this.image = value;
            this.ZoomExtents();
            this.minimumZoomFactor = this.zoomFactor / 10;
            this.MaximumZoomFactor = this.zoomFactor * 10;
        }
    }

    public ImageViewerViewMode ViewMode { get; set; }

    public ImageViewerZoomMode ZoomMode { get; set; }

    private ImageViewerLens Lens { get; set; }

    private float zoomFactor;
    private float minimumZoomFactor;
    private float maximumZoomFactor;
    private bool panning;
    private Point imageLocation;
    private Point imageTranslation;
    private Image image;
    private AreaSelection areaSelection;

    /// <summary>
    /// Class constructor
    /// </summary>
    public ImageViewer()
    {
        this.DoubleBuffered = true;
        this.MinimumZoomFactor = 0.1F;
        this.MaximumZoomFactor = 10F;
        this.ZoomStep = 2F;
        this.UseScannerUI = true;
        this.Lens = new ImageViewerLens();
        this.ViewMode = ImageViewerViewMode.PrintSelection;
        this.areaSelection = new AreaSelection();
        this.Controls.Add(this.areaSelection);

        // TWAIN

        // Initialise twain
        this.twain = new Twain(new WinFormsWindowMessageHook(this));
        // Try to set the last used default scanner
        if (this.AvailableScanners.Any())
        {
            this.twain.TransferImage += twain_TransferImage;
            this.twain.ScanningComplete += twain_ScanningComplete;
            if (!this.SetScanner(this.defaultScanner))
                this.SetScanner(this.AvailableScanners.First());
        }
    }

    /// <summary>
    /// Saves the currently loaded image under the specified filename, in the specified format at the specified quality
    /// </summary>
    /// <param name="FileName">The file name (full file path) under which to save the file. File type extension is not required.</param>
    /// <param name="Format">The file format under which to save the file</param>
    /// <param name="Quality">The quality in percent of the image to save. This is optional and may or may not be used have an effect depending on the chosen file type. Default is maximum quality.</param>
    public void SaveImage(string FileName, GraphicFormats Format, uint Quality = 100)
    {
        ImageCodecInfo encoder;
        EncoderParameters encoderParameters;

        if (FileName.IsNullOrEmpty())
            throw new ArgumentNullException(FileName);
        else
        {
            string extension = Path.GetExtension(FileName);
            if (!string.IsNullOrEmpty(extension))
                FileName = FileName.Replace(extension, string.Empty);
            FileName += "." + Format.ToString();
        }

        Quality = Math.Min(Math.Max(1, Quality), 100);

        if (!TryGetEncoder(Format, out encoder))
            return;

        encoderParameters = new EncoderParameters(1);
        encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (int)Quality);

        this.Image.Save(FileName, encoder, encoderParameters);
    }

    /// <summary>
    /// Tries to retrieve the appropriate encoder for the chose image format.
    /// </summary>
    /// <param name="Format">The image format for which to attempt retrieving the encoder</param>
    /// <param name="Encoder">The encoder object in which to store the encoder if found</param>
    /// <returns>True if the encoder was found, else false</returns>
    private bool TryGetEncoder(GraphicFormats Format, out ImageCodecInfo Encoder)
    {
        ImageCodecInfo[] codecs;

        codecs = ImageCodecInfo.GetImageEncoders();
        Encoder = codecs.First(c => c.FormatDescription.Equals(Format.ToString(), StringComparison.CurrentCultureIgnoreCase));

        return Encoder != null;
    }

    /// <summary>
    /// Set the zoom level to view the entire image in the control
    /// </summary>
    public void ZoomExtents()
    {
        if (this.Image == null)
            return;

        this.ZoomFactor = (float)Math.Min((double)this.Width / this.Image.Width, (double)this.Height / this.Image.Height);
        this.LimitBasePoint(imageLocation.X, imageLocation.Y);
        this.Invalidate();
    }

    /// <summary>
    /// Multiply the zoom
    /// </summary>
    /// <param name="NewZoomFactor">The zoom factor to set for the image</param>
    public void SetZoom(float NewZoomFactor)
    {
        this.SetZoom(NewZoomFactor, Point.Empty);
    }

    /// <summary>
    /// Multiply the zoom
    /// </summary>
    /// <param name="NewZoomFactor">The zoom factor to set for the image</param>
    /// <param name="ZoomLocation">The point in which to zoom in</param>
    public void SetZoom(float NewZoomFactor, Point ZoomLocation)
    {
        int x;
        int y;
        float multiplier;

        multiplier = NewZoomFactor / this.ZoomFactor;

        x = (int)((ZoomLocation.IsEmpty ? this.Width / 2 : ZoomLocation.X - imageLocation.X) / ZoomFactor);
        y = (int)((ZoomLocation.IsEmpty ? this.Height / 2 : ZoomLocation.Y - imageLocation.Y) / ZoomFactor);

        if ((multiplier < 1 && this.ZoomFactor > this.MinimumZoomFactor) || (multiplier > 1 && this.ZoomFactor < this.MaximumZoomFactor))
            ZoomFactor *= multiplier;
        else
            return;

        LimitBasePoint((int)(this.Width / 2 - x * ZoomFactor), (int)(this.Height / 2 - y * ZoomFactor));
        this.Invalidate();
    }

    /// <summary>
    /// Determines the base point for positioning the image
    /// </summary>
    /// <param name="x">The x coordinate based on which to determine the positioning</param>
    /// <param name="y">The y coordinate based on which to determine the positioning</param>
    private void LimitBasePoint(int x, int y)
    {
        int width;
        int height;

        if (this.Image == null)
            return;

        width = this.Width - (int)(Image.Width * ZoomFactor);
        height = this.Height - (int)(Image.Height * ZoomFactor);

        x = width < 0 ? Math.Max(Math.Min(x, 0), width) : width / 2;
        y = height < 0 ? Math.Max(Math.Min(y, 0), height) : height / 2;

        imageLocation = new Point(x, y);
    }

    /// <summary>
    /// Verify that the maximum and minimum zoom are correctly set
    /// </summary>
    private void SetZoomFactorLimits()
    {
        float maximum = this.MaximumZoomFactor;
        float minimum = this.minimumZoomFactor;

        this.maximumZoomFactor = Math.Max(maximum, minimum);
        this.minimumZoomFactor = Math.Min(maximum, minimum);
    }

    /// <summary>
    /// Mouse button down event
    /// </summary>
    protected override void OnMouseDown(MouseEventArgs e)
    {
        switch (this.ZoomMode)
        {
            case ImageViewerZoomMode.OnClick:
                switch (e.Button)
                {
                    case MouseButtons.Left:
                        this.SetZoom(this.ZoomFactor * this.ZoomStep, e.Location);
                        break;

                    case MouseButtons.Middle:
                        this.panning = true;
                        this.Cursor = Cursors.NoMove2D;
                        this.imageTranslation = e.Location;
                        break;

                    case MouseButtons.Right:
                        this.SetZoom(this.ZoomFactor / this.ZoomStep, e.Location);
                        break;
                }
                break;

            case ImageViewerZoomMode.Lens:
                if (e.Button == MouseButtons.Left)
                {
                    this.Cursor = Cursors.Cross;
                    this.Lens.Location = e.Location;
                    this.Lens.Visible = true;
                }
                else
                {
                    this.Cursor = Cursors.Default;
                    this.Lens.Visible = false;
                }
                this.Invalidate();
                break;
        }

        base.OnMouseDown(e);
    }

    /// <summary>
    /// Mouse button up event
    /// </summary>
    protected override void OnMouseUp(MouseEventArgs e)
    {
        switch (this.ZoomMode)
        {
            case ImageViewerZoomMode.OnClick:
                if (e.Button == MouseButtons.Middle)
                {
                    panning = false;
                    this.Cursor = Cursors.Default;
                }
                break;

            case ImageViewerZoomMode.Lens:
                break;
        }

        base.OnMouseUp(e);
    }

    /// <summary>
    /// Mouse move event
    /// </summary>
    protected override void OnMouseMove(MouseEventArgs e)
    {
        switch (this.ViewMode)
        {
            case ImageViewerViewMode.Normal:
                switch (this.ZoomMode)
                {
                    case ImageViewerZoomMode.OnClick:
                        if (panning)
                        {
                            LimitBasePoint(imageLocation.X + e.X - this.imageTranslation.X, imageLocation.Y + e.Y - this.imageTranslation.Y);
                            this.imageTranslation = e.Location;
                        }
                        break;

                    case ImageViewerZoomMode.Lens:
                        if (this.Lens.Visible)
                        {
                            this.Lens.Location = e.Location;
                        }
                        break;
                }
                break;

            case ImageViewerViewMode.PrintSelection:
                break;

            case ImageViewerViewMode.PrintPreview:
                break;
        }

        base.OnMouseMove(e);
    }

    /// <summary>
    /// Resize event
    /// </summary>
    protected override void OnResize(EventArgs e)
    {
        LimitBasePoint(imageLocation.X, imageLocation.Y);
        this.Invalidate();

        base.OnResize(e);
    }

    /// <summary>
    /// Paint event
    /// </summary>
    protected override void OnPaint(PaintEventArgs pe)
    {
        Rectangle src;
        Rectangle dst;

        pe.Graphics.Clear(this.BackColor);

        if (this.Image != null)
        {
            switch (this.ViewMode)
            {
                case ImageViewerViewMode.Normal:
                    src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height));
                    dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor)));
                    pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel);

                    this.Lens.Draw(pe.Graphics, this.Image, this.ZoomFactor, this.imageLocation);
                    break;

                case ImageViewerViewMode.PrintSelection:
                    src = new Rectangle(Point.Empty, new Size(Image.Width, Image.Height));
                    dst = new Rectangle(this.imageLocation, new Size((int)(this.Image.Width * this.ZoomFactor), (int)(this.Image.Height * this.ZoomFactor)));
                    pe.Graphics.DrawImage(this.Image, dst, src, GraphicsUnit.Pixel);
                    break;

                case ImageViewerViewMode.PrintPreview:
                    break;
            }
        }

        //Debug.WriteLine("Viewer redrawn " + DateTime.Now);
        base.OnPaint(pe);
    }
}

编辑3:

将高度设置为较大时会遇到与图形相关的问题。例如,如果在AreaSelection构造函数中我将高度设置为500,则拖动控件实际上会将绘图搞砸。

1 个答案:

答案 0 :(得分:1)

  

虽然拖动通过矩形显示的底层图像落后于

这是不可避免的,更新矩形也会重绘图像。如果那是昂贵的,比如超过30毫秒,那么这一点就会变得明显。

对于像现代机器上的图像这样简单的东西,这是很多毫秒。它可以花费那么长的唯一方法是当图像并且需要重新缩放以适合图片框时。并且像素格式与视频适配器的像素格式不兼容,因此必须将每个像素格式从图像像素格式转换为视频适配器的像素格式。这确实可以累加到几毫秒。

每次绘制图像时,您需要帮助避免PictureBox烧掉那么多cpu周期。通过预分割图像,将其从一个巨大的位图转换为更适合控件的图像。通过改变像素格式,32bppPArgb格式最好通过长镜头,因为它匹配绝大多数视频适配器的像素格式。它比其他所有格式都快十倍。您可以在this answer中找到用于进行此转换的样板代码。

相关问题