Windows 8: How to undo & redo ink using built in Inking functionality? - c#

I've implemented inking code in my app based on the simplified inking sample by microsoft : http://code.msdn.microsoft.com/windowsapps/Input-simplified-ink-sample-11614bbf/view/SourceCode
First I made a class that saves data of an operation (draw/delete/clear) like this:
public enum eInkOperation
{
Draw,
Delete,
None
}
public class InkOperation
{
public InkStroke Stroke { get; set; } //requred for drawing from undo
public eInkOperation Operation { get; set; }
public InkOperation(InkStroke stroke, eInkOperation inkOperation)
{
Stroke = stroke.Clone(); //needs to be cloned for AddStroke to work
Operation = inkOperation;
}
}
Then I made one stack for undo ink operations and one for redo operations
//stack of normal operations
Stack<InkOperation> _undoStack = new Stack<InkOperation>();
//Undo action will pop them off of the undo stack and push them onto the redo stack
Stack<InkOperation> _redoStack = new Stack<InkOperation>();
When a user undoes a stroke I push it on the redo stack and delete it from the inkmanager with these methods:
private void RedoStackPush(InkOperation inkOperation)
{
inkOperation.Stroke = inkOperation.Stroke.Clone();
_redoStack.Push(inkOperation);
}
private void DeleteStroke(InkStroke stroke)
{
stroke = inkManager.GetStrokes().Last();
stroke.Selected = true;
inkManager.DeleteSelected();
}
Then when the user clicks on redo, the stroke is popped off the redo stack and drawn using this method:
private void DrawStroke(InkStroke stroke)
{
if (stroke!=null)
{
inkManager.Mode = InkManipulationMode.Inking;
inkManager.AddStroke(stroke);
}
renderer.Clear(); //this renderer object smooths the strokes
//and adds them as Path objects to the desired control (Grid, etc)
renderer.AddInk(inkManager.GetStrokes());
}
This all works, and the stroke is displayed back on the grid.
However, when I try to erase the newly redrawn stroke I get this exception:
AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
This happens in:
public void PointerMoved(PointerRoutedEventArgs e)
{
try
{
var pointerPoint = e.GetCurrentPoint(_inkingArea);
var pointerEventType = InkHelpers.GetPointerEventType(e);
if (pointerId == (int)pointerPoint.PointerId)
{
switch (inkManager.Mode)
{
case InkManipulationMode.Inking:
case InkManipulationMode.Selecting:
//process intermediate points
var intermediatePoints = e.GetIntermediatePoints(_inkingArea);
for (int i = intermediatePoints.Count - 1; i >= 0; i--)
{
inkManager.ProcessPointerUpdate(intermediatePoints[i]);
}
//live rendering
renderer.UpdateLiveRender(pointerPoint);
break;
case InkManipulationMode.Erasing:
//check if something has been erased
//in erase mode InkManager.ProcessPointerUpdate returns an invalidate rectangle:
//if it is not degenerate, something has been erased
//in erase mode don't bother processing intermediate points
//If inkManager.ProcessPointerUpdate throws an exception, it crashes the app regardless of any catches
Rect invalidateRect = (Rect)inkManager.ProcessPointerUpdate(e.GetCurrentPoint(_inkingArea));
if (invalidateRect.Height != 0 && invalidateRect.Width != 0)
{
//we don't know what has been erased so we clear the render
//and add back all the ink saved in the ink manager
renderer.Clear();
var remainingStrokes = inkManager.GetStrokes();
renderer.AddInk(remainingStrokes);
}
break;
default:
break;
}
}
}
catch (Exception) { }
}
At this line:
Rect invalidateRect = (Rect)inkManager.ProcessPointerUpdate(e.GetCurrentPoint(_inkingArea));
I think the problem lies in the process of adding strokes to the ink manager. I tried making a new stroke and even inheriting from the InkStroke to make it customizable but the InkStroke class is sealed and it doesn't have a constructor. The only was I found to copy it was to do inkStroke.Clone(). But even that has its problems when trying to redraw deleted ink (undo a deleted stroke).
I tried to make this question as clear as possible using the least amount of code to avoid confusion, so let me know if it's insufficient.
Also in this question I'm focusing on undoing a draw action. Undoing an erasing action (or even "clear all" action) has its own set of problems because I can't make a copy of the InkStroke object.
Thanks in advance for your time and consideration.

See this thread from MSDN, it might be helpful.

Related

how to temporarily pause drawing updates of a realtime data chart

I would like a “Pause” the chart's series updates to do some job (like i have a button when i click it will suspend the chart update and then when I click resume button, it will update all suspended point in series.
I know about
chart1.Series.SuspendUpdates();
but it does not seem to work with me. I use mschart sample -- realtime data (thread safe).
Here is the full code
public partial class RealTimeSample : Form
{
public RealTimeSample()
{
InitializeComponent();
}
private Thread addDataRunner;
private Random rand = new Random();
public delegate void AddDataDelegate();
public AddDataDelegate addDataDel;
private void RealTimeSample_Load(object sender, System.EventArgs e)
{
// create the Adding Data Thread but do not start until start button clicked
ThreadStart addDataThreadStart = new ThreadStart(AddDataThreadLoop);
addDataRunner = new Thread(addDataThreadStart);
// create a delegate for adding data
addDataDel += new AddDataDelegate(AddData);
}
/// Main loop for the thread that adds data to the chart.
/// The main purpose of this function is to Invoke AddData
/// function every 1000ms (1 second).
private void AddDataThreadLoop()
{
while (true)
{
chart1.Invoke(addDataDel);
Thread.Sleep(1000);
}
}
public void AddData()
{
DateTime timeStamp = DateTime.Now;
foreach (Series ptSeries in chart1.Series)
{
AddNewPoint(timeStamp, ptSeries);
}
}
/// The AddNewPoint function is called for each series in the chart when
/// new points need to be added. The new point will be placed at specified
/// X axis (Date/Time) position with a Y value in a range +/- 1 from the previous
/// data point's Y value, and not smaller than zero.
public void AddNewPoint(DateTime timeStamp, System.Windows.Forms.DataVisualization.Charting.Series ptSeries)
{
double newVal = 0;
if (ptSeries.Points.Count > 0)
{
newVal = ptSeries.Points[ptSeries.Points.Count - 1].YValues[0] + ((rand.NextDouble() * 2) - 1);
}
if (newVal < 0)
newVal = 0;
// Add new data point to its series.
chart1.Series.SuspendUpdates();
ptSeries.Points.AddXY(timeStamp.ToOADate(), rand.Next(10, 20));
chart1.Series.SuspendUpdates();
// remove all points from the source series older than 1.5 minutes.
double removeBefore = timeStamp.AddSeconds((double)(90) * (-1)).ToOADate();
//remove oldest values to maintain a constant number of data points
while (ptSeries.Points[0].XValue < removeBefore)
{
ptSeries.Points.RemoveAt(0);
}
chart1.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddMinutes(2).ToOADate();
}
/// Clean up any resources being used.
protected override void Dispose(bool disposing)
{
if ((addDataRunner.ThreadState & ThreadState.Suspended) == ThreadState.Suspended)
{
addDataRunner.Resume();
}
addDataRunner.Abort();
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
private void startTrending_Click_1(object sender, EventArgs e)
{
// Disable all controls on the form
startTrending.Enabled = false;
// and only Enable the Stop button
stopTrending.Enabled = true;
// Predefine the viewing area of the chart
var minValue = DateTime.Now;
var maxValue = minValue.AddSeconds(120);
chart1.ChartAreas[0].AxisX.Minimum = minValue.ToOADate();
chart1.ChartAreas[0].AxisX.Maximum = maxValue.ToOADate();
// Reset number of series in the chart.
chart1.Series.Clear();
// create a line chart series
Series newSeries = new Series("Series1");
newSeries.ChartType = SeriesChartType.Line;
newSeries.BorderWidth = 2;
newSeries.Color = Color.OrangeRed;
newSeries.XValueType = ChartValueType.DateTime;
chart1.Series.Add(newSeries);
// start worker threads.
if (addDataRunner.IsAlive == true)
{
addDataRunner.Resume();
}
else
{
addDataRunner.Start();
}
}
private void stopTrending_Click_1(object sender, EventArgs e)
{
if (addDataRunner.IsAlive == true)
{
addDataRunner.Suspend();
}
// Enable all controls on the form
startTrending.Enabled = true;
// and only Disable the Stop button
stopTrending.Enabled = false;
}
}
EDIT:
I figured out that as long as you set the minmum or the maximum property for the Axis the chart will keep display even if you have used
chart1.Series.SuspendUpdates();
I had to to remove those lines after i call SuspendUpdates() and now i can see the chart series suspended
chart1.ChartAreas[0].AxisX.Minimum = ptSeries.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = DateTime.FromOADate(ptSeries.Points[0].XValue).AddMinutes(2).ToOADate();
MsChart does support this directly and indeed using Series.SuspendUpdates() is a good way but you need to do it right. (See however the update below for a drawback)
MSDN says this:
A call to the Invalidate method will have no effect after the
SuspendUpdates method is called.
If you call the SuspendUpdates method several times, you will need to
call the ResumeUpdates method an equal number of times.
This would explain why it doesn't work for you: Keeping the calls balanced is crucial. You need to keep track of them yourself as there is no counter you could query. But if you overshoot the ResumeUpdates calls, nothing bad happens, extra calls are simply ignored and the next SuspendUpdates will pause again.
Here is an example screenshot, watch the suspension counter..!
Note that normally adding points will automatically triggger an Invalidate. If you are doing other things, like drawing in a Paint event etc.. you may need to call Chart.Invalidate(), which SuspendUpdates will prevent, until cancelled by the same number of ResumeUpdates..
Alternatively you can also use one of these simple workarounds:
The most straightforward will create the DataPoints via a constructor and then either
use series.Add(theNewPoint) for normal, or..
use someList<DataPoint>.Add(theNewPoint) for paused mode.
When setting to pause mode simply add all points to the series.Points before clearing it. Unfortunately there is no points.AddRange so you will have to use a foreach loop. Maybe chart.SuspendLayout could help with performance.
The other workaround that comes to mind may or may not be suitable: You could play with the xAxis.Maximum and maybe xAxis.Minimum values. By setting them to fixed values you would allow addding points to the right without displaying them. To show the whole set of points you would reset them to double.NaN. This may work for you but it may also interfer with what you have.
Update: As noted by OP, the data are updated when he changes the Minimum and/or Maximum of an Axis. The same effect will show on many other occasions:
Calling chart.AreasRecalculateAxesScale();
Changing the chart's Size
Changing any Axis property like Color or Width..
Changing the LegendText of a Series
and many more..
So I guess the updated data are needed whenever the ChartArea is manipulated and forced to update itself..
So, this may well make the 1st workaround the better because the more robust solution.

Getting resources Image path from Picturebox [duplicate]

I have a program which has 16 grid tiles using picturebox but only uses 5 images, the rest of the tiles are just a black image.
I would like to be able to tell which image the 'user' clicks on.
I have a method called image_Click(object sender, EventArgs e)
I have an if statement inside this method that states:
if (peckedSquare.BackColor == Color.Black)
{
System.Diagnostics.Debug.WriteLine("Pecked a black square");
return;
}
This sends a String that lets me know when a black square has been clicked.
Is there an easy way to perhaps say:
//pseudo code:
if (peckedSquare.ImageName == pigeon1.png)
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
I have googled my query but I have not found any suitable answers.
//EDIT
I have just re-read my code.
I was assigning each picture to a picturebox square using a randomnumber.
I had this random number as a variable, so I can just use that variable to determine which image was clicked.
ie.
if (randomNumber == 1)
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
or better than that
pigeonSelected = randomNumber + 1 //as I am using an array to store the images
System.Diagnostics.Debug.WriteLine("Pecked Pigeon Number {0}", pigeonSelected);
As quick & dirty solution I would use Tag property for that, null for black tiles and file path for the others (and it's always available, even if your image comes from resources), something like this:
if (peckedSquare.Tag == null)
{
Debug.WriteLine("Pecked a black square");
}
else
{
switch (Path.GetFileName(peckedSquare.Tag.ToString()))
{
case "pigeon1.png":
break;
}
}
Of course when you create tiles you have to store file path in Tag:
PictureBox tile = new PictureBox();
tile.Image = Image.FromFile(path); // Or another source, of course
tile.Tag = path;
As alternative you may even use Name property for this, each control is named (primary for designer-code integration) but if you create controls at run-time you can set that value to anything you want (not only valid identifiers). Same usage as above just no need to call ToString().
How to Improve?
Please let me say this solution is not very OOP. Even without a big refactoring we can do little bit better. Note that you can store whatever you want in Tag property. A number, a simple string unrelated to file name or even (maybe better) a class or an enum that represents that image (to delegate action to that object). This is a very raw example:
abstract class Tile {
public abstract void Activate();
}
sealed class EmptyTile : Tile {
public virtual void Activate() {
Debug.WriteLine("Pecked a black square");
}
}
sealed class ImageTile : Tile {
public ImageTile(string content) {
_content = content;
}
public virtual void Activate() {
Debug.WriteLine(_content);
}
private string _content;
}
In this way in your click event handler you can do this:
((Tile)peckedTile.Tag).Activate();
No need to check what's inside or to compare with null. No if and no switch, just don't forget to put proper object (ImageTile or BlackTile) when you create tiles.
Use PictureBox.Load(string) method to load images from file. Then the file path will be stored in the
PictureBox.ImageLocation property:
A call to the Load method will overwrite the ImageLocation property, setting ImageLocation to the URL value specified in the method call.
So you can write for example:
if (peckedSquare.ImageLocation.EndsWith("pigeon1.png"))
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
I think you could do something like the following:
private List<PictureBox> pictures = null;
string[] ImageNames = new string[]
{
"images\\test_1.jpg",
"images\\test_2.jpg"
};
private void Form1_Load(object sender, EventArgs e)
{
pictures = new List<PictureBox>();
for (var idx = 0; idx < ImageNames.Length; idx++)
{
pictures.Add(new PictureBox());
pictures[idx].Image = new Bitmap(ImageNames[idx]);
pictures[idx].Click += OnClick;
// you'll want to set the offset and everything so it shows at the right place
Controls.Add(pictures[idx]);
}
}
private void OnClick(object sender, EventArgs eventArgs)
{
// you'll definitely want error handling here
var ImageName = ImageNames[pictures.IndexOf((PictureBox) sender)];
}
You can see that in the click method you will be able to get the image name, which is what you are looking for I believe.
As others have said, you can also use the "Tag" property assuming you weren't already using this for some other purpose. The nice thing about tag is you can probably also edit it through the form designer which allows you to lay things out a little more nicely than the automatic layout I used above. Good luck!
You can do something like this using the Tag property like #Adriano suggested:
public Form1()
{
InitializeComponent();
pictureBox1.Click += pictureBox_Click;
pictureBox2.Click += pictureBox_Click;
pictureBox3.Click += pictureBox_Click;
// ...
pictureBox1.Tag = "picture1.png";
pictureBox2.Tag = "picture2.png";
pictureBox3.Tag = "picture3.png";
}
void pictureBox_Click(object sender, EventArgs e)
{
PictureBox pb = sender as PictureBox;
if (pb.BackColor != Color.Black)
Debug.WriteLine(pb.Tag.ToString());
else
Debug.WriteLine("Black image");
}

Xamarin IOS Load View form Gesture Event

I am trying to capture the PanGesture event, so when the user swipe with his/her finger anywhere in the view to the right, for example, the RightView shows up and when to left, the LeftView shows up.
I am using the code below. It is from Xamarin docs. I modified it a bit. My 'currView' is my outlet for the current View. When I swipe at the simulator with my mouse, the event gets captured correctly, but when stepping through (after break pointing) at the curly bracket of
if (recognizer.State != (UIGestureRecognizerState.Cancelled | UIGestureRecognizerState.Failed
{
The code loops between it and this one.
HandleDrag(gesture))
once for every pixel (I think).
I am trying to trigger the load of another view, but didn't find a spot to place it at. I tried at the
Console.WriteLine ("Here")
and at:
currView.AddGestureRecognizer (gesture);
but every time the code finishes the loop, it just ignores it and execution ends. How can I make the loading of next view work?
The line of the comment:
// NEED TO LOAD ANOTHER VIEW HERE
shows my desires spot to load another View.
Here is my code
namespace Example_Touch.Screens.iPhone.GestureRecognizers
{
using System;
using System.Drawing;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
public partial class GestureRecognizers_iPhone : UIViewController
{
private RectangleF originalImageFrame = RectangleF.Empty;
// The IntPtr and initWithCoder constructors are required for items that need
// to be able to be created from a xib rather than from managed code
public GestureRecognizers_iPhone(IntPtr handle)
: base(handle)
{
Initialize();
}
[Export("initWithCoder:")]
public GestureRecognizers_iPhone(NSCoder coder)
: base(coder)
{
Initialize();
}
public GestureRecognizers_iPhone()
: base("GestureRecognizers_iPhone", null)
{
Initialize();
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
WireUpDragGestureRecognizer();
}
protected void WireUpDragGestureRecognizer()
{
// create a new tap gesture
UIPanGestureRecognizer gesture = new UIPanGestureRecognizer();
// wire up the event handler (have to use a selector)
gesture.AddTarget(() => HandleDrag(gesture));
currView.AddGestureRecognizer (gesture);
}
protected void HandleDrag(UIPanGestureRecognizer recognizer)
{
PointF offset2 = recognizer.TranslationInView(currView);
// if it's just began, cache the location of the image
if (recognizer.State != (UIGestureRecognizerState.Cancelled | UIGestureRecognizerState.Failed
| UIGestureRecognizerState.Possible))
{
Console.WriteLine ("Here");
// NEED TO LOAD ANOTHER VIEW HERE
}
}
private void Initialize()
{
}
}
}
From Apple documentation:
A panning gesture is continuous. It begins (UIGestureRecognizerStateBegan) when the minimum number of fingers allowed (minimumNumberOfTouches) has moved enough to be considered a pan. It changes (UIGestureRecognizerStateChanged) when a finger moves while at least the minimum number of fingers are pressed down. It ends (UIGestureRecognizerStateEnded) when all fingers are lifted.
Check for UIGestureRecognizerState.Ended state and if it occurs, you can load another view.
From your description it seems that you could use UISwipeGestureRecognizer instead of UIPanGestureRecognizer. The UISwipeGestureRecognizer is a discrete gesture, so you don't have to check for states, you just implement it this way:
Somewhere in your code implement the handler:
private void HandleSwipeRight()
{
// load another view
Console.WriteLine("Swipe right.");
}
And initialize the gesture recognzier in ViewDidLoad():
UISwipeGestureRecognizer swipeGestureRecognizerRight = new UISwipeGestureRecognizer(HandleSwipeRight);
swipeGestureRecognizerRight.Direction = UISwipeGestureRecognizerDirection.Right;
this.View.AddGestureRecognizer(swipeGestureRecognizerRight);
You can add another swipe gesture recognizer for a different direction.

How do you access the name of the image in a picturebox in Visual Studio using C#

I have a program which has 16 grid tiles using picturebox but only uses 5 images, the rest of the tiles are just a black image.
I would like to be able to tell which image the 'user' clicks on.
I have a method called image_Click(object sender, EventArgs e)
I have an if statement inside this method that states:
if (peckedSquare.BackColor == Color.Black)
{
System.Diagnostics.Debug.WriteLine("Pecked a black square");
return;
}
This sends a String that lets me know when a black square has been clicked.
Is there an easy way to perhaps say:
//pseudo code:
if (peckedSquare.ImageName == pigeon1.png)
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
I have googled my query but I have not found any suitable answers.
//EDIT
I have just re-read my code.
I was assigning each picture to a picturebox square using a randomnumber.
I had this random number as a variable, so I can just use that variable to determine which image was clicked.
ie.
if (randomNumber == 1)
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
or better than that
pigeonSelected = randomNumber + 1 //as I am using an array to store the images
System.Diagnostics.Debug.WriteLine("Pecked Pigeon Number {0}", pigeonSelected);
As quick & dirty solution I would use Tag property for that, null for black tiles and file path for the others (and it's always available, even if your image comes from resources), something like this:
if (peckedSquare.Tag == null)
{
Debug.WriteLine("Pecked a black square");
}
else
{
switch (Path.GetFileName(peckedSquare.Tag.ToString()))
{
case "pigeon1.png":
break;
}
}
Of course when you create tiles you have to store file path in Tag:
PictureBox tile = new PictureBox();
tile.Image = Image.FromFile(path); // Or another source, of course
tile.Tag = path;
As alternative you may even use Name property for this, each control is named (primary for designer-code integration) but if you create controls at run-time you can set that value to anything you want (not only valid identifiers). Same usage as above just no need to call ToString().
How to Improve?
Please let me say this solution is not very OOP. Even without a big refactoring we can do little bit better. Note that you can store whatever you want in Tag property. A number, a simple string unrelated to file name or even (maybe better) a class or an enum that represents that image (to delegate action to that object). This is a very raw example:
abstract class Tile {
public abstract void Activate();
}
sealed class EmptyTile : Tile {
public virtual void Activate() {
Debug.WriteLine("Pecked a black square");
}
}
sealed class ImageTile : Tile {
public ImageTile(string content) {
_content = content;
}
public virtual void Activate() {
Debug.WriteLine(_content);
}
private string _content;
}
In this way in your click event handler you can do this:
((Tile)peckedTile.Tag).Activate();
No need to check what's inside or to compare with null. No if and no switch, just don't forget to put proper object (ImageTile or BlackTile) when you create tiles.
Use PictureBox.Load(string) method to load images from file. Then the file path will be stored in the
PictureBox.ImageLocation property:
A call to the Load method will overwrite the ImageLocation property, setting ImageLocation to the URL value specified in the method call.
So you can write for example:
if (peckedSquare.ImageLocation.EndsWith("pigeon1.png"))
{
System.Diagnostics.Debug.WriteLine("Pecked Pigeon number 1");
}
I think you could do something like the following:
private List<PictureBox> pictures = null;
string[] ImageNames = new string[]
{
"images\\test_1.jpg",
"images\\test_2.jpg"
};
private void Form1_Load(object sender, EventArgs e)
{
pictures = new List<PictureBox>();
for (var idx = 0; idx < ImageNames.Length; idx++)
{
pictures.Add(new PictureBox());
pictures[idx].Image = new Bitmap(ImageNames[idx]);
pictures[idx].Click += OnClick;
// you'll want to set the offset and everything so it shows at the right place
Controls.Add(pictures[idx]);
}
}
private void OnClick(object sender, EventArgs eventArgs)
{
// you'll definitely want error handling here
var ImageName = ImageNames[pictures.IndexOf((PictureBox) sender)];
}
You can see that in the click method you will be able to get the image name, which is what you are looking for I believe.
As others have said, you can also use the "Tag" property assuming you weren't already using this for some other purpose. The nice thing about tag is you can probably also edit it through the form designer which allows you to lay things out a little more nicely than the automatic layout I used above. Good luck!
You can do something like this using the Tag property like #Adriano suggested:
public Form1()
{
InitializeComponent();
pictureBox1.Click += pictureBox_Click;
pictureBox2.Click += pictureBox_Click;
pictureBox3.Click += pictureBox_Click;
// ...
pictureBox1.Tag = "picture1.png";
pictureBox2.Tag = "picture2.png";
pictureBox3.Tag = "picture3.png";
}
void pictureBox_Click(object sender, EventArgs e)
{
PictureBox pb = sender as PictureBox;
if (pb.BackColor != Color.Black)
Debug.WriteLine(pb.Tag.ToString());
else
Debug.WriteLine("Black image");
}

Keep track of screen change and screen resolution change in Windows Form Application to change form size

I want to change form size depending on Screen and it's resolution.
What I want is a correct event to track these screen changes as well as screen resolution changes at runtime.
In other words,
If user is using two screens and move application to another screen, that should be tracked and change size accordingly, i.e. reduce size if new screen's resolution is low or increase size if resolution is larger.
Also track screen resolution change on the same screen, and make changes to size accordingly.
I know how to change Form size, get current screen and it's resolution, just need these events to keep track of these changes.
Going over this answer I've decided to improve it and add further information to form a more complete solution.
The Challenge
Tracking which screen a Form is currently being rendered on. This can change if a user drags the form to another monitor or unplugs a monitor. The resolution can change if a user manually drags a window to a different display or changes the resolution directly.
Firstly, tracking form location. We need to hook into a Move event for the form context, fortunately the .Net framework provides such an event, and it is named Control.Move Event.
Secondly, we will need to hook into a screen resolution changed event, we can do this with the SystemEvents.DisplaySettingsChanged event.
And putting it together, I got this:
struct Resolution
{
public int Width;
public int Height;
}
int previous = -1;
int current = -1;
private bool CheckScreenChanged()
{
bool changed = false;
current = GetScreenIndex();
if (current != -1 && previous != -1 && current != previous) // form changed screen.
{
changed = true;
}
previous = current;
return changed;
}
private int GetScreenIndex()
{
return Array.IndexOf(Screen.AllScreens, Screen.FromControl(this));
}
private Resolution GetCurrentResolution()
{
Screen screen = Screen.FromControl(this);
Resolution res = new Resolution();
res.Width = screen.Bounds.Width;
res.Height = screen.Bounds.Height;
return res;
}
private void SetResolutionLabel()
{
Resolution res = GetCurrentResolution();
label2.Text = String.Format("Width: {0}, Height: {1}", res.Width, res.Height);
}
private void ScreenChanged()
{
label1.Text = "Screen " + current.ToString();
}
private void Form_Moved(object sender, System.EventArgs e)
{
bool changed = CheckScreenChanged();
if (changed == true)
{
ScreenChanged();
SetResolutionLabel();
}
}
public void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
SetResolutionLabel();
}
public void Initialize()
{
this.Move += Form_Moved;
SystemEvents.DisplaySettingsChanged += new
EventHandler(SystemEvents_DisplaySettingsChanged);
previous = GetScreenIndex();
current = GetScreenIndex();
ScreenChanged();
SetResolutionLabel();
}
The code above is tested on a simple form with two labels called label1 and label2, which are updated when the screen the form is on changes or the resolution changes.
An image of this in action on my primary screen/display
And on my secondary screen/display when the form has been dragged to it:

Categories