I have a problem with drawing image on form background. I have a form where there are inserted both scrollbars (H and V). Because I need to be able display image in original size I use them for scrolling it but when I scroll to maximum right or bottom on both sides missing 7 pixels which are hidden under scrollbars. There is sample code:
private int PosX, PosY;
this.Map = new Bitmap(TestLines.Properties.Resources.mapa);
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
if (this.Map != null)
{
e.Graphics.DrawImageUnscaled(Map, new Point(this.PosX, this.PosY));
int MapResX = (int)((float)this.Map.Width / this.Map.HorizontalResolution * e.Graphics.DpiX);
int MapResY = (int)((float)this.Map.Height / this.Map.VerticalResolution * e.Graphics.DpiY);
if (MapResX > this.ClientSize.Width && MapResY > this.ClientSize.Height - this.toolStrip1.Height)
{
hScrollBar1.Minimum = 0;
hScrollBar1.Maximum = MapResX - this.ClientSize.Width + vScrollBar1.Width;
hScrollBar1.Visible = true;
vScrollBar1.Minimum = 0;
vScrollBar1.Maximum = MapResY - this.ClientSize.Height + toolStrip1.Height + hScrollBar1.Height;
vScrollBar1.Visible = true;
}
else if (MapResX > this.ClientSize.Width)
{
hScrollBar1.Minimum = 0;
hScrollBar1.Maximum = MapResX - this.ClientSize.Width;
hScrollBar1.Visible = true;
vScrollBar1.Visible = false;
}
else if (MapResY > this.ClientSize.Height - this.toolStrip1.Height)
{
vScrollBar1.Minimum = 0;
vScrollBar1.Maximum = MapResY - this.ClientSize.Height + toolStrip1.Height;
vScrollBar1.Visible = true;
hScrollBar1.Visible = false;
}
else
{
hScrollBar1.Visible = false;
vScrollBar1.Visible = false;
}
}
}
Note that there is also a toolstrip where i do not draw. And then simple scrollbars actions:
private void hScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
this.PosX = -e.NewValue;
this.Invalidate(false);
this.Update();
}
private void vScrollBar1_Scroll(object sender, ScrollEventArgs e)
{
this.PosY = toolStrip1.Height -e.NewValue;
this.Invalidate(false);
this.Update();
}
Can you describe me why this happens ?
This is just not the right way to go about it. Create your own control instead, using Panel as the base class so you get the scrolling for free. Add a new class to your project and paste the code shown below. Compile. Drop it from the top of the toolbox onto your form, you probably want to set its Dock property to Fill. Assign the Map property, either with the designer or in your code.
using System;
using System.Drawing;
using System.Windows.Forms;
class MapPanel : Panel {
public MapPanel() {
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
private Image map;
public Image Map {
get { return map; }
set {
map = value;
this.AutoScrollMinSize = value == null ? Size.Empty : value.Size;
this.Invalidate();
}
}
protected override void OnPaintBackground(PaintEventArgs e) {
base.OnPaintBackground(e);
if (map != null) {
e.Graphics.TranslateTransform(this.AutoScrollPosition.X, this.AutoScrollPosition.Y);
e.Graphics.DrawImage(map, 0, 0);
}
}
}
Related
I have implemented a rubber band by adopting the following code:
https://support.microsoft.com/en-gb/kb/314945
This is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Reflection;
namespace Test
{
public partial class TestX: UserControl
{
private Boolean m_bLeftButton { get; set; }
private Boolean m_bMiddleButton { get; set; }
private Boolean m_bZoomWindow { get; set; }
Point m_ptOriginal = new Point();
Point m_ptLast = new Point();
public TestX()
{
m_bZoomWindow = false;
m_bLeftButton = false;
m_bMiddleButton = false;
}
// Called when the left mouse button is pressed.
public void MyMouseDown(Object sender, MouseEventArgs e)
{
if(m_bZoomWindow && e.Button == MouseButtons.Left)
{
// Make a note that we "have the mouse".
m_bLeftButton = true;
// Store the "starting point" for this rubber-band rectangle.
m_ptOriginal.X = e.X;
m_ptOriginal.Y = e.Y;
// Special value lets us know that no previous
// rectangle needs to be erased.
m_ptLast.X = -1;
m_ptLast.Y = -1;
}
}
// Convert and normalize the points and draw the reversible frame.
private void MyDrawReversibleRectangle(Point p1, Point p2)
{
Rectangle rc = new Rectangle();
// Convert the points to screen coordinates.
p1 = PointToScreen(p1);
p2 = PointToScreen(p2);
// Normalize the rectangle.
if (p1.X < p2.X)
{
rc.X = p1.X;
rc.Width = p2.X - p1.X;
}
else
{
rc.X = p2.X;
rc.Width = p1.X - p2.X;
}
if (p1.Y < p2.Y)
{
rc.Y = p1.Y;
rc.Height = p2.Y - p1.Y;
}
else
{
rc.Y = p2.Y;
rc.Height = p1.Y - p2.Y;
}
// Draw the reversible frame.
ControlPaint.DrawReversibleFrame(rc,
Color.WhiteSmoke, FrameStyle.Thick);
}
// Called when the left mouse button is released.
public void MyMouseUp(Object sender, MouseEventArgs e)
{
if(m_bZoomWindow && e.Button == MouseButtons.Left)
{
// Set internal flag to know we no longer "have the mouse".
m_bZoomWindow = false;
m_bLeftButton = false;
// If we have drawn previously, draw again in that spot
// to remove the lines.
if (m_ptLast.X != -1)
{
Point ptCurrent = new Point(e.X, e.Y);
MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);
// Do zoom now ...
}
// Set flags to know that there is no "previous" line to reverse.
m_ptLast.X = -1;
m_ptLast.Y = -1;
m_ptOriginal.X = -1;
m_ptOriginal.Y = -1;
}
}
// Called when the mouse is moved.
public void MyMouseMove(Object sender, MouseEventArgs e)
{
Point ptCurrent = new Point(e.X, e.Y);
if(m_bLeftButton)
{
// If we "have the mouse", then we draw our lines.
if (m_bZoomWindow)
{
// If we have drawn previously, draw again in
// that spot to remove the lines.
if (m_ptLast.X != -1)
{
MyDrawReversibleRectangle(m_ptOriginal, m_ptLast);
}
// Update last point.
if(ptCurrent != m_ptLast)
{
m_ptLast = ptCurrent;
// Draw new lines.
MyDrawReversibleRectangle(m_ptOriginal, ptCurrent);
}
}
}
}
// Set up delegates for mouse events.
protected override void OnLoad(System.EventArgs e)
{
MouseDown += new MouseEventHandler(MyMouseDown);
MouseUp += new MouseEventHandler(MyMouseUp);
MouseMove += new MouseEventHandler(MyMouseMove);
MouseWheel += new MouseEventHandler(MyMouseWheel);
m_bZoomWindow = false;
}
}
}
It itself it works, but the rectangle flashes. Other programs, like CAD packages, have zero flickering when drawing a rectangle.
My form is set to use DoubleBuffering so I thought it would be OK. Has anyone else encountered this issue?
Update: I thought I would go back to the beginning and do a test winforms project with a table layout panel and a embedded user control. I set the user control to work like the answer I was provided. The only difference was that I set the user control constructor like this:
public MyUserControl()
{
InitializeComponent();
_selectionPen = new Pen(Color.Black, 3.0f);
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
BackColor = Color.Transparent;
Dock = DockStyle.Fill;
Margin = new Padding(1);
}
Notice the additional control styles AllPaintingInWmPaint and UserPaint? It seems that I needed these in addition to the OptimizedDoubleBuffer style. I also set the form as double buffered.
When I make those adjustments I can draw a flicker free rubber band. Hoorah! But when I add back in the embedded class for rendering a DWG in the user control, I get the conflict of one over imposing the other.
So that is where I am at and I will wait to see what I can glean from the vendors of the DWG viewer class.
To demonstrate using the Paint event, from my comment to the OP.
Smoothest box draw I was able to get was by doing it in Paint and setting ControlStyles.OptimizeDoubleBuffer on the control. Of course, it depends on the intended bounds of your box -- this will not exceed the bounds of the control itself (i.e. will not draw onto the form or desktop):
using System.Drawing;
using System.Windows.Forms;
namespace WinformsScratch.RubberBand
{
public class TestY : Control
{
private Point? _selectionStart;
private Point? _selectionEnd;
private readonly Pen _selectionPen;
public TestY()
{
_selectionPen = new Pen(Color.Black, 3.0f);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
MouseDown += (s, e) => {
if (e.Button == MouseButtons.Left)
_selectionStart = _selectionEnd = e.Location;
};
MouseUp += (s, e) => {
if (e.Button == MouseButtons.Left)
{
_selectionStart = _selectionEnd = null;
Invalidate(false);
}
};
MouseMove += (s, e) => {
if (_selectionStart.HasValue &&
_selectionEnd.HasValue &&
_selectionEnd.Value != e.Location)
{
_selectionEnd = e.Location;
Invalidate(false);
}
};
Paint += (s, e) => {
if (_selectionStart.HasValue && _selectionEnd.HasValue)
e.Graphics.DrawRectangle(_selectionPen, GetSelectionRectangle());
};
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (_selectionPen != null) _selectionPen.Dispose();
}
base.Dispose(disposing);
}
private Rectangle GetSelectionRectangle()
{
Rectangle rc = new Rectangle();
if (_selectionStart.HasValue && _selectionEnd.HasValue)
{
// Normalize the rectangle.
if (_selectionStart.Value.X < _selectionEnd.Value.X)
{
rc.X = _selectionStart.Value.X;
rc.Width = _selectionEnd.Value.X - _selectionStart.Value.X;
}
else
{
rc.X = _selectionEnd.Value.X;
rc.Width = _selectionStart.Value.X - _selectionEnd.Value.X;
}
if (_selectionStart.Value.Y < _selectionEnd.Value.Y)
{
rc.Y = _selectionStart.Value.Y;
rc.Height = _selectionEnd.Value.Y - _selectionStart.Value.Y;
}
else
{
rc.Y = _selectionEnd.Value.Y;
rc.Height = _selectionStart.Value.Y - _selectionEnd.Value.Y;
}
}
return rc;
}
}
}
I had to implement a way to navigate between different element that are stacked in xamarin.forms. In the code below, you can see that we already manage the ability to distinguish every single one of the child of the container. At the moment, what I have written works but I need some help figuring out if it’s the good way or if there is a better way or even, if I can improve what I’ve done.
Currently, when you swipe from left to right or whatever move, I just use a SimpleOnGestureListener.
Here are some samples of what we are currently achieving:
I declared my custom elements such as the container & the children and then I created the customRenderer for each of them in an android library (only working on android at the moment).
“MyContent” is simply a modified frame with a specific Draw method and an event for each move available (up, down, left, right).
Code of the container
using System;
using Xamarin.Forms;
namespace Elements
{
public class MyContainer : Layout<MyContent>
{
public MyContainer ()
{
}
protected override void LayoutChildren (double x, double y, double width, double height)
{
for (int i = 0; i < Children.Count ; i++) {
MyContent child = Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
int soffset = Children.Count - i;
int eoffset = soffset - 1;
Rectangle startBoundingRegion = new Rectangle (12 * soffset + Padding.Left, 14 * soffset + Padding.Top, width - 24 * soffset, height - 8 * soffset);
Rectangle endBoundingRegion = new Rectangle (12 * eoffset + Padding.Left, 14 * eoffset + Padding.Top, width - 24 * eoffset, height - 8 * eoffset);
child.OriginalBounds = endBoundingRegion;
LayoutChildIntoBoundingRegion (child,startBoundingRegion);
child.LayoutTo(endBoundingRegion,100,Easing.Linear);
}
}
protected override SizeRequest OnSizeRequest (double widthConstraint, double heightConstraint)
{
var height = 0;
var minHeight = 0;
var width = 0;
var minWidth = 0;
for (int i = 0; i < Children.Count; i++) {
MyContent child = Children[i];
// skip invisible children
if(!child.IsVisible)
continue;
var childSizeRequest = child.GetSizeRequest (double.PositiveInfinity, height);
height = (int)Math.Max (height, childSizeRequest.Minimum.Height);
minHeight = (int)Math.Max (minHeight, childSizeRequest.Minimum.Height);
width += (int)childSizeRequest.Request.Width;
minWidth += (int)childSizeRequest.Minimum.Width;
}
return new SizeRequest (new Size (width, height), new Size (minWidth, minHeight));
}
}
}
Code of its renderer
{using ...}
[assembly: ExportRenderer (typeof(MyContainer), typeof(MyContainerRenderer))]
namespace MyApp
{
public class MyContainerRenderer : ViewRenderer<Layout<MyContent>,Android.Views.View>
{
private readonly MyContentGestureListener listener;
private readonly GestureDetector detector;
private bool isAnimating;
private MyContent currentMyContent;
public MyContainerRenderer ()
{
listener = new MyContentGestureListener();
detector = new GestureDetector(listener);
}
protected override void OnElementChanged (ElementChangedEventArgs<Layout<MyContent>> e)
{
base.OnElementChanged(e);
if (e.NewElement == null)
{
this.Touch -= HandleTouch;
listener.OnSwipeLeft -= HandleOnSwipeLeft;
listener.OnSwipeRight -= HandleOnSwipeRight;
listener.OnSwipeTop -= HandleOnSwipeTop;
listener.OnSwipeDown -= HandleOnSwipeDown;
listener.OnPanVertical -= HandleOnPanVertical;
listener.OnPanHorizontal -= HandleOnPanHorizontal;
}
if (e.OldElement == null)
{
this.Touch += HandleTouch;
listener.OnSwipeLeft += HandleOnSwipeLeft;
listener.OnSwipeRight += HandleOnSwipeRight;
listener.OnSwipeTop += HandleOnSwipeTop;
listener.OnSwipeDown += HandleOnSwipeDown;
listener.OnPanVertical += HandleOnPanVertical;
listener.OnPanHorizontal += HandleOnPanHorizontal;
}
}
void HandleTouch(object sender, TouchEventArgs e)
{
currentMyContent = ((MyContainer)this.Element).Children.Last();
if (!isAnimating) {
if (e.Event.Action == MotionEventActions.Down) {
currentMyContent.ScaleTo (1.01, 100, Easing.Linear);
} else if ( e.Event.Action == MotionEventActions.Up){
if (!isAnimating) {
isAnimating = true;
currentMyContent.ScaleTo (1, 100, Easing.Linear);
currentMyContent.LayoutTo (currentMyContent.OriginalBounds, 100, Easing.CubicIn);
isAnimating = false;
listener.ResetFlags ();
}
}
detector.OnTouchEvent (e.Event);
}
}
void HandleOnPanVertical(object sender, EventArgs distanceY)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y - ((EventArgs<float>)distanceY).Value,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
currentMyContent.Layout(dest);
}
void HandleOnPanHorizontal(object sender, EventArgs distanceX)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X - ((EventArgs<float>)distanceX).Value,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
currentMyContent.Layout (dest);
}
async void animateNext (MyContent mContent, Rectangle dest)
{
isAnimating = true;
await mContent.LayoutTo (dest, 150, Easing.Linear);
((MyContainer)this.Element).Children.Remove (mContent);
((MyContainer)this.Element).Children.Insert (0, mContent);
isAnimating = false;
}
void HandleOnSwipeLeft(object sender, EventArgs e)
{
currentMyContent.OnSwipeLeft();
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Left -= Width;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeRight(object sender, EventArgs e)
{
currentMyContent.OnSwipeRight();
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Left += Width;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeTop(object sender, EventArgs e)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X, currentMyContent.Bounds.Y, currentMyContent.Bounds.Width, currentMyContent.Bounds.Height);
dest.Top -= Height;
animateNext (currentMyContent, dest);
}
void HandleOnSwipeDown(object sender, EventArgs e)
{
Rectangle dest = new Rectangle (currentMyContent.Bounds.X,currentMyContent.Bounds.Y,currentMyContent.Bounds.Width,currentMyContent.Bounds.Height);
dest.Top += Height;
animateNext (currentMyContent, dest);
}
}
class MyContentGestureListener : GestureDetector.SimpleOnGestureListener
{
private static int SWIPE_THRESHOLD = 50;
private static int SWIPE_VELOCITY_THRESHOLD = 20;
private bool isPanningV;
private bool isPanningH;
public event EventHandler OnSwipeDown;
public event EventHandler OnSwipeTop;
public event EventHandler OnSwipeLeft;
public event EventHandler OnSwipeRight;
public event EventHandler OnPanHorizontal;
public event EventHandler OnPanVertical;
public override bool OnScroll(MotionEvent ev1, MotionEvent ev2,float distanceX,float distanceY)
{
if (Math.Abs (distanceX) > Math.Abs (distanceY) && OnPanHorizontal != null && !isPanningV) {
isPanningH = true;
OnPanHorizontal (this,new EventArgs<float> (distanceX));
} else if (OnPanVertical != null && !isPanningH) {
isPanningV = true;
OnPanVertical (this, new EventArgs<float> (distanceY));
}
return base.OnScroll (ev1, ev2, distanceX, distanceY);
}
public override bool OnFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
{
float diffY = e2.GetY() - e1.GetY();
float diffX = e2.GetX() - e1.GetX();
if (Math.Abs(diffX) > Math.Abs(diffY) && Math.Abs(diffX) > SWIPE_THRESHOLD && Math.Abs(velocityX) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffX > 0 && OnSwipeRight != null)
{
OnSwipeRight (this, null);
isPanningH = false;
}
else if (OnSwipeLeft != null) {
OnSwipeLeft (this, null);
isPanningH = false;
}
}
else if (Math.Abs(diffY) > SWIPE_THRESHOLD && Math.Abs(velocityY) > SWIPE_VELOCITY_THRESHOLD)
{
if (diffY > 0 && OnSwipeDown != null)
{
OnSwipeDown (this, null);
isPanningV = false;
}
else if (OnSwipeTop != null) {
OnSwipeTop (this, null);
isPanningV = false;
}
}
return base.OnFling (e1, e2, velocityX, velocityY);
}
public void ResetFlags(){
isPanningH = false;
isPanningV = false;
}
}
public class EventArgs<T> : EventArgs
{
public EventArgs(T value)
{
Value = value;
}
public T Value { get; private set; }
}
}
I have a control (System.Windows.Forms.ScrollableControl) which can potentially be very large. It has custom OnPaint logic. For that reason, I am using the workaround described here.
public class CustomControl : ScrollableControl
{
public CustomControl()
{
this.AutoScrollMinSize = new Size(100000, 500);
this.DoubleBuffered = true;
}
protected override void OnScroll(ScrollEventArgs se)
{
base.OnScroll(se);
this.Invalidate();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
graphics.Clear(this.BackColor);
...
}
}
The painting code mainly draws "normal" things that move when you scroll. The origin of each shape that is drawn is offsetted by this.AutoScrollPosition.
graphics.DrawRectangle(pen, 100 + this.AutoScrollPosition.X, ...);
However, the control also contains "static" elements, which are always drawn at the same position relative to the parent control. For that, I just don't use AutoScrollPosition and draw the shapes directly:
graphics.DrawRectangle(pen, 100, ...);
When the user scrolls, Windows translates the entire visible area in the direction opposite to the scrolling. Usually this makes sense, because then the scrolling seems smooth and responsive (and only the new part has to be redrawn), however the static parts are also affected by this translation (hence the this.Invalidate() in OnScroll). Until the next OnPaint call has successfully redrawn the surface, the static parts are slightly off. This causes a very noticable "shaking" effect when scrolling.
Is there a way I can create a scrollable custom control that does not have this problem with static parts?
You could do this by taking full control of scrolling. At the moment, you're just hooking in to the event to do your logic. I've faced issues with scrolling before, and the only way I've ever managed to get everything to work smoothly is by actually handling the Windows messages by overriding WndProc. For instance, I have this code to synchronize scrolling between several ListBoxes:
protected override void WndProc(ref Message m) {
base.WndProc(ref m);
// 0x115 and 0x20a both tell the control to scroll. If either one comes
// through, you can handle the scrolling before any repaints take place
if (m.Msg == 0x115 || m.Msg == 0x20a)
{
//Do you scroll processing
}
}
Using WndProc will get you the scroll messages before anything gets repainted at all, so you can appropriately handle the static objects. I'd use this to suspend scrolling until an OnPaint occurs. It won't look as smooth, but you won't have issues with the static objects moving.
Since I really needed this, I ended up writing a Control specifically for the case when you have static graphics on a scrollable surface (whose size can be greater than 65535).
It is a regular Control with two ScrollBar controls on it, and a user-assignable Control as its Content. When the user scrolls, the container sets its Content's AutoScrollOffset accordingly. Therefore, it is possible to use controls which use the AutoScrollOffset method for drawing without changing anything. The Content's actual size is exactly the visible part of it at all times. It allows horizontal scrolling by holding down the shift key.
Usage:
var container = new ManuallyScrollableContainer();
var content = new ExampleContent();
container.Content = content;
container.TotalContentWidth = 150000;
container.TotalContentHeight = 5000;
container.Dock = DockStyle.Fill;
this.Controls.Add(container); // e.g. add to Form
Code:
It became a bit lengthy, but I could avoid ugly hacks. Should work with mono. I think it turned out pretty sane.
public class ManuallyScrollableContainer : Control
{
public ManuallyScrollableContainer()
{
InitializeControls();
}
private class UpdatingHScrollBar : HScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private class UpdatingVScrollBar : VScrollBar
{
protected override void OnValueChanged(EventArgs e)
{
base.OnValueChanged(e);
// setting the scroll position programmatically shall raise Scroll
this.OnScroll(new ScrollEventArgs(ScrollEventType.EndScroll, this.Value));
}
}
private ScrollBar shScrollBar;
private ScrollBar svScrollBar;
public ScrollBar HScrollBar
{
get { return this.shScrollBar; }
}
public ScrollBar VScrollBar
{
get { return this.svScrollBar; }
}
private void InitializeControls()
{
this.Width = 300;
this.Height = 300;
this.shScrollBar = new UpdatingHScrollBar();
this.shScrollBar.Top = this.Height - this.shScrollBar.Height;
this.shScrollBar.Left = 0;
this.shScrollBar.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
this.svScrollBar = new UpdatingVScrollBar();
this.svScrollBar.Top = 0;
this.svScrollBar.Left = this.Width - this.svScrollBar.Width;
this.svScrollBar.Anchor = AnchorStyles.Right | AnchorStyles.Top | AnchorStyles.Bottom;
this.shScrollBar.Width = this.Width - this.svScrollBar.Width;
this.svScrollBar.Height = this.Height - this.shScrollBar.Height;
this.Controls.Add(this.shScrollBar);
this.Controls.Add(this.svScrollBar);
this.shScrollBar.Scroll += this.HandleScrollBarScroll;
this.svScrollBar.Scroll += this.HandleScrollBarScroll;
}
private Control _content;
/// <summary>
/// Specifies the control that should be displayed in this container.
/// </summary>
public Control Content
{
get { return this._content; }
set
{
if (_content != value)
{
RemoveContent();
this._content = value;
AddContent();
}
}
}
private void AddContent()
{
if (this.Content != null)
{
this.Content.Left = 0;
this.Content.Top = 0;
this.Content.Width = this.Width - this.svScrollBar.Width;
this.Content.Height = this.Height - this.shScrollBar.Height;
this.Content.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right;
this.Controls.Add(this.Content);
CalculateMinMax();
}
}
private void RemoveContent()
{
if (this.Content != null)
{
this.Controls.Remove(this.Content);
}
}
protected override void OnParentChanged(EventArgs e)
{
// mouse wheel events only arrive at the parent control
if (this.Parent != null)
{
this.Parent.MouseWheel -= this.HandleMouseWheel;
}
base.OnParentChanged(e);
if (this.Parent != null)
{
this.Parent.MouseWheel += this.HandleMouseWheel;
}
}
private void HandleMouseWheel(object sender, MouseEventArgs e)
{
this.HandleMouseWheel(e);
}
/// <summary>
/// Specifies how the control reacts to mouse wheel events.
/// Can be overridden to adjust the scroll speed with the mouse wheel.
/// </summary>
protected virtual void HandleMouseWheel(MouseEventArgs e)
{
// The scroll difference is calculated so that with the default system setting
// of 3 lines per scroll incremenet,
// one scroll will offset the scroll bar value by LargeChange / 4
// i.e. a quarter of the thumb size
ScrollBar scrollBar;
if ((Control.ModifierKeys & Keys.Shift) != 0)
{
scrollBar = this.HScrollBar;
}
else
{
scrollBar = this.VScrollBar;
}
var minimum = 0;
var maximum = scrollBar.Maximum - scrollBar.LargeChange;
if (maximum <= 0)
{
// happens when the entire area is visible
return;
}
var value = scrollBar.Value - (int)(e.Delta * scrollBar.LargeChange / (120.0 * 12.0 / SystemInformation.MouseWheelScrollLines));
scrollBar.Value = Math.Min(Math.Max(value, minimum), maximum);
}
public event ScrollEventHandler Scroll;
protected virtual void OnScroll(ScrollEventArgs e)
{
var handler = this.Scroll;
if (handler != null)
{
handler(this, e);
}
}
/// <summary>
/// Event handler for the Scroll event of either scroll bar.
/// </summary>
private void HandleScrollBarScroll(object sender, ScrollEventArgs e)
{
OnScroll(e);
if (this.Content != null)
{
this.Content.AutoScrollOffset = new System.Drawing.Point(-this.HScrollBar.Value, -this.VScrollBar.Value);
this.Content.Invalidate();
}
}
private int _totalContentWidth;
public int TotalContentWidth
{
get { return _totalContentWidth; }
set
{
if (_totalContentWidth != value)
{
_totalContentWidth = value;
CalculateMinMax();
}
}
}
private int _totalContentHeight;
public int TotalContentHeight
{
get { return _totalContentHeight; }
set
{
if (_totalContentHeight != value)
{
_totalContentHeight = value;
CalculateMinMax();
}
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
CalculateMinMax();
}
private void CalculateMinMax()
{
if (this.Content != null)
{
// Reduced formula according to
// http://msdn.microsoft.com/en-us/library/system.windows.forms.scrollbar.maximum.aspx
// Note: The original formula is bogus.
// According to the article, LargeChange has to be known in order to calculate Maximum,
// however, that is not always possible because LargeChange cannot exceed Maximum.
// If (LargeChange) == (1 * visible part of control), the formula can be reduced to:
if (this.TotalContentWidth > this.Content.Width)
{
this.shScrollBar.Enabled = true;
this.shScrollBar.Maximum = this.TotalContentWidth;
}
else
{
this.shScrollBar.Enabled = false;
}
if (this.TotalContentHeight > this.Content.Height)
{
this.svScrollBar.Enabled = true;
this.svScrollBar.Maximum = this.TotalContentHeight;
}
else
{
this.svScrollBar.Enabled = false;
}
// this must be set after the maximum is determined
this.shScrollBar.LargeChange = this.shScrollBar.Width;
this.shScrollBar.SmallChange = this.shScrollBar.LargeChange / 10;
this.svScrollBar.LargeChange = this.svScrollBar.Height;
this.svScrollBar.SmallChange = this.svScrollBar.LargeChange / 10;
}
}
}
Example content:
public class ExampleContent : Control
{
public ExampleContent()
{
this.DoubleBuffered = true;
}
static Random random = new Random();
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var graphics = e.Graphics;
// random color to make the clip rectangle visible in an unobtrusive way
var color = Color.FromArgb(random.Next(160, 180), random.Next(160, 180), random.Next(160, 180));
graphics.Clear(color);
Debug.WriteLine(this.AutoScrollOffset.X.ToString() + ", " + this.AutoScrollOffset.Y.ToString());
CheckerboardRenderer.DrawCheckerboard(
graphics,
this.AutoScrollOffset,
e.ClipRectangle,
new Size(50, 50)
);
StaticBoxRenderer.DrawBoxes(graphics, new Point(0, this.AutoScrollOffset.Y), 100, 30);
}
}
public static class CheckerboardRenderer
{
public static void DrawCheckerboard(Graphics g, Point origin, Rectangle bounds, Size squareSize)
{
var numSquaresH = (bounds.Width + squareSize.Width - 1) / squareSize.Width + 1;
var numSquaresV = (bounds.Height + squareSize.Height - 1) / squareSize.Height + 1;
var startBoxH = (bounds.X - origin.X) / squareSize.Width;
var startBoxV = (bounds.Y - origin.Y) / squareSize.Height;
for (int i = startBoxH; i < startBoxH + numSquaresH; i++)
{
for (int j = startBoxV; j < startBoxV + numSquaresV; j++)
{
if ((i + j) % 2 == 0)
{
Random random = new Random(i * j);
var color = Color.FromArgb(random.Next(70, 95), random.Next(70, 95), random.Next(70, 95));
var brush = new SolidBrush(color);
g.FillRectangle(brush, i * squareSize.Width + origin.X, j * squareSize.Height + origin.Y, squareSize.Width, squareSize.Height);
brush.Dispose();
}
}
}
}
}
public static class StaticBoxRenderer
{
public static void DrawBoxes(Graphics g, Point origin, int boxWidth, int boxHeight)
{
int height = origin.Y;
int left = origin.X;
for (int i = 0; i < 25; i++)
{
Rectangle r = new Rectangle(left, height, boxWidth, boxHeight);
g.FillRectangle(Brushes.White, r);
g.DrawRectangle(Pens.Black, r);
height += boxHeight;
}
}
}
This is the code:
private void hsMagnfier_OnMouseDown(object sender)
{
int x = mLastCursorPosition.X;
int y = mLastCursorPosition.Y;
MagnifierForm magnifier = new MagnifierForm(mConfiguration, System.Windows.Forms.Cursor.Position);//mLastCursorPosition);
magnifier.Show();
}
This code above is in a Form which I can drag over the screen.
Then when I click on an icon it's doing the magnifier.Show(); and the magnifier form is shown up where the mouse current position is.
But if I click on it again so now the position of the new form the magnifier is in my Form1 center. And not where the mouse current position as in the first time.
This is the MagnifierForm code maybe first time it's in the current mouse position but in the next time/s it's in the center of Form1 ?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.IO;
using System.Drawing.Imaging;
namespace ScreenVideoRecorder
{
public partial class MagnifierForm : Form
{
public MagnifierForm(Configuration configuration, Point startPoint)
{
InitializeComponent();
//--- My Init ---
mConfiguration = configuration;
FormBorderStyle = FormBorderStyle.None;
ShowInTaskbar = mConfiguration.ShowInTaskbar;
TopMost = mConfiguration.TopMostWindow;
Width = mConfiguration.MagnifierWidth;
Height = mConfiguration.MagnifierHeight;
// Make the window (the form) circular
GraphicsPath gp = new GraphicsPath();
gp.AddEllipse(ClientRectangle);
Region = new Region(gp);
mImageMagnifier = Properties.Resources.magnifierGlass;
mTimer = new Timer();
mTimer.Enabled = true;
mTimer.Interval = 20;
mTimer.Tick += new EventHandler(HandleTimer);
mScreenImage = new Bitmap(Screen.PrimaryScreen.Bounds.Width,
Screen.PrimaryScreen.Bounds.Height);
mStartPoint = startPoint;
mTargetPoint = startPoint;
if (mConfiguration.ShowInTaskbar)
ShowInTaskbar = true;
else
ShowInTaskbar = false;
}
protected override void OnShown(EventArgs e)
{
RepositionAndShow();
}
private delegate void RepositionAndShowDelegate();
private void RepositionAndShow()
{
if (InvokeRequired)
{
Invoke(new RepositionAndShowDelegate(RepositionAndShow));
}
else
{
// Capture the screen image now!
Graphics g = Graphics.FromImage(mScreenImage);
g.CopyFromScreen(0, 0, 0, 0, new Size(mScreenImage.Width, mScreenImage.Height));
g.Dispose();
if (mConfiguration.HideMouseCursor)
Cursor.Hide();
else
Cursor = Cursors.Cross;
Capture = true;
if (mConfiguration.RememberLastPoint)
{
mCurrentPoint = mLastMagnifierPosition;
Cursor.Position = mLastMagnifierPosition;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
}
else
{
mCurrentPoint = Cursor.Position;
}
Show();
}
}
void HandleTimer(object sender, EventArgs e)
{
float dx = mConfiguration.SpeedFactor * (mTargetPoint.X - mCurrentPoint.X);
float dy = mConfiguration.SpeedFactor * (mTargetPoint.Y - mCurrentPoint.Y);
if (mFirstTime)
{
mFirstTime = false;
mCurrentPoint.X = mTargetPoint.X;
mCurrentPoint.Y = mTargetPoint.Y;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
return;
}
mCurrentPoint.X += dx;
mCurrentPoint.Y += dy;
if (Math.Abs(dx) < 1 && Math.Abs(dy) < 1)
{
mTimer.Enabled = false;
}
else
{
// Update location
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
mLastMagnifierPosition = new Point((int)mCurrentPoint.X, (int)mCurrentPoint.Y);
}
Refresh();
}
protected override void OnMouseDown(MouseEventArgs e)
{
mOffset = new Point(Width / 2 - e.X, Height / 2 - e.Y);
mCurrentPoint = PointToScreen(new Point(e.X + mOffset.X, e.Y + mOffset.Y));
mTargetPoint = mCurrentPoint;
mTimer.Enabled = true;
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (mConfiguration.CloseOnMouseUp)
{
Close();
mScreenImage.Dispose();
}
Cursor.Show();
Cursor.Position = mStartPoint;
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
mTargetPoint = PointToScreen(new Point(e.X + mOffset.X, e.Y + mOffset.Y));
mTimer.Enabled = true;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
if (mConfiguration.DoubleBuffered)
{
// Do not paint background (required for double buffering)!
}
else
{
base.OnPaintBackground(e);
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (mBufferImage == null)
{
mBufferImage = new Bitmap(Width, Height);
}
Graphics bufferGrf = Graphics.FromImage(mBufferImage);
Graphics g;
if (mConfiguration.DoubleBuffered)
{
g = bufferGrf;
}
else
{
g = e.Graphics;
}
if (mScreenImage != null)
{
Rectangle dest = new Rectangle(0, 0, Width, Height);
int w = (int)(Width / mConfiguration.ZoomFactor);
int h = (int)(Height / mConfiguration.ZoomFactor);
int x = Left - w / 2 + Width / 2;
int y = Top - h / 2 + Height / 2;
g.DrawImage(
mScreenImage,
dest,
x, y,
w, h,
GraphicsUnit.Pixel);
}
if (mImageMagnifier != null)
{
g.DrawImage(mImageMagnifier, 0, 0, Width, Height);
}
if (mConfiguration.DoubleBuffered)
{
e.Graphics.DrawImage(mBufferImage, 0, 0, Width, Height);
}
}
//--- Data Members ---
#region Data Members
private Timer mTimer;
private Configuration mConfiguration;
private Image mImageMagnifier;
private Image mBufferImage = null;
private Image mScreenImage = null;
private Point mStartPoint;
private PointF mTargetPoint;
private PointF mCurrentPoint;
private Point mOffset;
private bool mFirstTime = true;
private static Point mLastMagnifierPosition = Cursor.Position;
#endregion
}
}
The first time the new Form the magnifier is shown up where my mouse cursour is.
The next time i click on it's showing the magnifier form in the center of Form1 and not where the mouse cursour is.
Why is that ? When i clikc on the icon again it's still doing the
System.Windows.Forms.Cursor.Position
Again. Strange.
Consider you have two forms - Master and Child
If you are calling Child from Master on MouseUp event(for example), write the code in MouseUp event of Master form
ChildForm obj=new ChildForm();
obj.pntLocation = new Point(Cursor.Position.X, Cursor.Position.Y);
obj.ShowDialog();
Declare a variable inside the Child for location
public Point pntLocation;
Now set location inside the Form_Load of Child
this.Location = pntLocation;
Ok found that the part that doing it is here in the Magnifier form:
mConfiguration.RememberLastPoint = false;
if (mConfiguration.RememberLastPoint)
{
mCurrentPoint = mLastMagnifierPosition;
Cursor.Position = mLastMagnifierPosition;
Left = (int)mCurrentPoint.X - Width / 2;
Top = (int)mCurrentPoint.Y - Height / 2;
}
else
{
mCurrentPoint = Cursor.Position;
}
So I added for now the line: mConfiguration.RememberLastPoint = false; which did the job for now.
I have a class that extends TabControl, basically to have one with the tabs down the left hand side instead of along the top. To do I have set it to be custom drawn.
The problem is when this is put onto a form via the designer, it easily makes the designer loose track of itself and just give up and display nothing. To fix it I have to close the designer and re-open it and then everything is fine until I hit debug, or I go to a code window for a while and come back, where it gives up again.
Is there anything I can do to help visual studio a bit? As while its not throwing errors, it is getting a little tedious now. I'm using visual studio 2008.
Here is the code that extends the tab control, if anyone sees any issues that could be causing this it'd be very appreciated.
public class VerticalTabControl : TabControl
{
private Color tabColour1 = Color.AliceBlue;
public Color TabColour1
{
get { return tabColour1; }
set { tabColour1 = value; this.Refresh(); }
}
private Color tabColour2 = Color.White;
public Color TabColour2
{
get { return tabColour2; }
set { tabColour2 = value; this.Refresh(); }
}
private Color selectedTabColor1 = Color.AliceBlue;
public Color SelectedTabColor1
{
get { return selectedTabColor1; }
set { selectedTabColor1 = value; this.Refresh(); }
}
private Color selectedTabColor2 = Color.White;
public Color SelectedTabColor2
{
get { return selectedTabColor2; }
set { selectedTabColor2 = value; this.Refresh(); }
}
private Color backgroundColour = Color.White;
public Color BackgroundColour
{
get { return backgroundColour; }
set { backgroundColour = value; this.Refresh(); }
}
private Color tabTextColour = Color.Black;
public Color TabTextColour
{
get { return tabTextColour; }
set { tabTextColour = value; this.Refresh(); }
}
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
this.Parent.Resize += new EventHandler(Parent_Resize);
}
void Parent_Resize(object sender, EventArgs e)
{
this.Refresh();
}
public VerticalTabControl()
: base()
{
this.Alignment = TabAlignment.Left;
this.SizeMode = TabSizeMode.Fixed;
this.ItemSize = new Size(50, 120);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
this.DrawItem += new DrawItemEventHandler(VerticalTabControl_DrawItem);
}
void VerticalTabControl_DrawItem(object sender, DrawItemEventArgs e)
{
Graphics g = e.Graphics;
TabControl ctrl = sender as TabControl;
String sText = ctrl.TabPages[e.Index].Text;
Rectangle r = new Rectangle(e.Bounds.Left, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height);
if (e.Index == ctrl.SelectedIndex)
{
using (LinearGradientBrush gb = new LinearGradientBrush(r, this.selectedTabColor1, this.selectedTabColor2, LinearGradientMode.Horizontal))
{
e.Graphics.FillRectangle(gb, r);
}
}
else
{
using (LinearGradientBrush gb = new LinearGradientBrush(r, this.tabColour1, this.tabColour2, LinearGradientMode.Horizontal))
{
e.Graphics.FillRectangle(gb, r);
}
}
// Set up the page and the various pieces.
TabPage page = ctrl.TabPages[e.Index];
// Set up the offset for an icon, the bounding rectangle and image size and then fill the background.
int iconOffset = 0;
Rectangle tabBackgroundRect = e.Bounds;
// If we have images, process them.
if (this.ImageList != null)
{
// Get sice and image.
Size size = this.ImageList.ImageSize;
Image icon = null;
if (page.ImageIndex > -1)
icon = this.ImageList.Images[page.ImageIndex];
else if (page.ImageKey != "")
icon = this.ImageList.Images[page.ImageKey];
// If there is an image, use it.
if (icon != null)
{
Point startPoint = new Point(tabBackgroundRect.X + 6,
tabBackgroundRect.Y + 2 + ((tabBackgroundRect.Height - size.Height) / 2));
e.Graphics.DrawImage(icon, new Rectangle(startPoint, size));
iconOffset = size.Width + 4;
}
}
// Draw out the label.
SizeF sizeText = g.MeasureString(sText, ctrl.Font);
int iX = e.Bounds.Left + 6 + iconOffset;
int iY = e.Bounds.Top + (e.Bounds.Height / 2) - (int)(sizeText.Height / 2);
using (Brush ForeBrush = new SolidBrush(tabTextColour))
{
g.DrawString(sText, ctrl.Font, ForeBrush, iX, iY);
}
Rectangle rec = ctrl.GetTabRect(ctrl.TabPages.Count - 1);
Rectangle recF = new Rectangle(0, rec.Bottom, this.ItemSize.Height, ctrl.Height - rec.Bottom);
using (SolidBrush bb = new SolidBrush(backgroundColour))
{
g.FillRectangle(bb, recF);
}
}
}
turns out this was the offending bit of code
protected override void OnParentChanged(EventArgs e)
{
base.OnParentChanged(e);
this.Parent.Resize += new EventHandler(Parent_Resize);
}
'this.Parent' was sometimes null and so an exception would have been thrown which would have caused the designer to fail.
Have now fixed this and also tidied up the event hooking so I didn't leave hooks everywhere. Seems to work fine now. Thanks for your help whoever answered this and deleted their answer for some reason.