Making text move with the rectangle it is on top of - c#

I am new to silverlight and so I may just be missing something due to my lack of knowledge. I am writing an app that uses Rectangles that you can click and drag to around the screen after they are generated. I have function that generates the rectangles:
public void formatBox(block b)
{
Rectangle Rec = new Rectangle();
Rec.Height = 100;
Rec.Width = 200;
SolidColorBrush newBrush = new SolidColorBrush();
newBrush.Color = b.blockColor;
SolidColorBrush blackBrush = new SolidColorBrush();
blackBrush.Color = Colors.Black;
Rec.StrokeThickness = 4;
Rec.Stroke = blackBrush;
Rec.Fill = newBrush;
Canvas.SetTop(Rec, generateY(b.locationY));
Canvas.SetLeft(Rec, generateX(b.locationX));
TextBlock blockname = new TextBlock();
blockname.Text = b.blockText;
blockname.FontSize = 25;
canvas1.Children.Add(Rec);
canvas1.Children.Add(blockname);
Binding topbinding = new Binding("Top");
Binding leftbinding = new Binding("Left");
topbinding.Mode = BindingMode.OneWay;
leftbinding.Mode = BindingMode.OneWay;
topbinding.Source = Rec.GetValue(Canvas.TopProperty);
leftbinding.Source = Rec.GetValue(Canvas.LeftProperty);
blockname.SetBinding(Canvas.TopProperty, topbinding);
blockname.SetBinding(Canvas.LeftProperty, leftbinding);
Rec.MouseLeftButtonDown += new MouseButtonEventHandler(Handle_MouseDown);
Rec.MouseMove += new MouseEventHandler(Handle_MouseMove);
Rec.MouseLeftButtonUp += new MouseButtonEventHandler(Handle_MouseUp);
}
These rectangles are built from a block class
public class block
{
public double locationX { get; set; }
public double locationY { get; set; }
public Color blockColor { get; set; }
public string blockText { get; set; }
public block(double x, double y, Color c, string s)
{
this.locationX = x;
this.locationY = y;
this.blockColor = c;
this.blockText = s;
}
}
And my mouse event handlers:
bool isMouseCaptured;
double mouseVerticalPosition;
double mouseHorizontalPosition;
public void Handle_MouseDown(object sender, MouseEventArgs args)
{
Rectangle item = sender as Rectangle;
mouseVerticalPosition = args.GetPosition(null).Y;
mouseHorizontalPosition = args.GetPosition(null).X;
isMouseCaptured = true;
item.CaptureMouse();
}
public void Handle_MouseMove(object sender, MouseEventArgs args)
{
Rectangle item = sender as Rectangle;
if (isMouseCaptured)
{
// Calculate the current position of the object.
double deltaV = args.GetPosition(null).Y - mouseVerticalPosition;
double deltaH = args.GetPosition(null).X - mouseHorizontalPosition;
double newTop = deltaV + (double)item.GetValue(Canvas.TopProperty);
double newLeft = deltaH + (double)item.GetValue(Canvas.LeftProperty);
// Set new position of object.
item.SetValue(Canvas.TopProperty, newTop);
item.SetValue(Canvas.LeftProperty, newLeft);
// Update position global variables.
mouseVerticalPosition = args.GetPosition(null).Y;
mouseHorizontalPosition = args.GetPosition(null).X;
}
}
public void Handle_MouseUp(object sender, MouseEventArgs args)
{
Rectangle item = sender as Rectangle;
isMouseCaptured = false;
item.ReleaseMouseCapture();
mouseVerticalPosition = -1;
mouseHorizontalPosition = -1;
}
The text I am trying to move is called blockname in formatBox(). I have tried Binding as you can see here but I think there is a gap in my knowledge of an easier way to do this. Either way I would like the text to move when the block moves under it when the mouse event handlers are triggered. How do I get the text to move with it?

Since you have mousehandlers already you could just skip the binding and use code. (The binding wouldn't work as expected anyway: the text would have the same coordinates as the reactangle so it gets drawn over the top/left lines of the rectangle. This will look ugly and make the text hard to read, you need to offset the text so that it's inside the reactangle or outside). Basically what you would do is in MouseDown put a flag high to idicate the mouse was pressed, and record the point where the mouse is. Then in MouseMove you check the flag: if it is on, calculate the new position for your rectangle as it's currentposition + the distance moved from the point recorded in MouseDown. The position of the text would then be the new position + some offset.
btw I suggest to split methods like formatBox into multiple smaller methods and choose better names for your variables: it will make the code not only more readable, but also more maintainable.
edit
to figure out which rectangle to move, do a hit test on all your elements in the MouseDwon handler. Something like this:
Rectangle rectangleUnderMouse = null;
foreach( var rec in rectangles )
{
if( VisualTreeHelper.HitTest( rec, pointWhereMouseIs ) )
{
rectangleUnderMouse = rec;
break;
}
}
edit
sorry I didn't see you asked which textblock to move.. That is easier: you could keep a Dictionary<Rectangle,TextBlock> inside your main class:
public void formatBox(block b)
{
//...
myDictionary[ Rec ] = textblock;
}
public void Handle_MouseMove( object sender, MouseEventArgs args )
{
//...
textBlockForThisRect = myDictionary[ item ];
//move textBlockForThisRect
}

Related

confused over casting between custom object and UIElement

my prog adds objects to a canvas of type grid, instances are called vgridcargo.
I can remove them by double tapping no problem.
However, they also populate a listview and would like to remove them from canvas from here also.
the issue I am having is how to a determine the index location of that specific vgridcargo (Grid) object in the canvas.
Any help is greatly appreciated. Thanks
If I cast it to a UIElement all items come back as -1
var objectToDelete = sender as UIElement;
//string objID = objectToDelete.vCargoID;
ShowMessage("element is at index" + _canvasref.Children.IndexOf(objectToDelete));
if I leave it as I get
"Cannot Convert from VGridCargo to Windows.UI.XAML.UI.Element"
var objectToDelete = sender as VGridCargo;
//string objID = objectToDelete.vCargoID;
ShowMessage("element is at index" + _canvasref.Children.IndexOf(objectToDelete));
`vgridcargo`. Class
class VGridCargo
{
private Grid cargo;
private Canvas _canvasRef;
private double vCargoWidth;
private double vCargoHeight;
private Brush vCargoColour;
public bool vCargoHeavy;
public string vCargoDG;
public string vCargoID;
public string vCargoImgPath;
public double vTonnage;
private bool isActive;
// private static Dictionary<string,object> = new
private TextBlock vText = new TextBlock();
private Windows.UI.Input.PointerPoint startPoint;
private Point transOrigin = new Point(0.5, 0.5);
private RotateTransform cargoRotate = new RotateTransform
{
CenterX=0,
CenterY=0,
Angle=90,
};
/*public VGridCargo(Canvas canvas)
{
_canvasRef = canvas;
}*/
public VGridCargo(Canvas canvas, double inHeight, double inWidth, Brush inColor, bool inHLift, string inPath, string inID,double inTonnage)
{
_canvasRef = canvas;
vCargoWidth = inWidth;
vCargoHeight = inHeight;
vCargoColour = inColor;
vCargoHeavy = inHLift;
//vCargoDG = inDG;
vCargoImgPath = inPath;
vCargoID = inID;
vTonnage = inTonnage;
}
public void CreateCargo()
{
cargo = new Grid();
//Properties
cargo.Height = Ratioconvert(vCargoHeight);
cargo.Width = Ratioconvert(vCargoWidth);
cargo.Name = vCargoID;
cargo.Children.Add(vText);
vText.FontSize = 5;
vText.Text= vCargoID;
//** Look into sort out color's and brush's in program mixed api's?
Color myColor = (vCargoColour as SolidColorBrush).Color;
cargo.Background = new SolidColorBrush(myColor);
cargo.CanDrag = true;
//Handlers
cargo.RightTapped += Cargo_RightTapped;
cargo.PointerPressed += Cargo_PointerPressed;
cargo.PointerMoved += Cargo_PointerMoved;
cargo.PointerReleased += Cargo_PointerReleased;
cargo.DoubleTapped += Cargo_DoubleTapped;
// vText.PointerEntered += VText_PointerEntered;
//vText.PointerExited += VText_PointerExited;
Canvas.SetLeft(cargo, 0);
Canvas.SetTop(cargo, 0);
_canvasRef.Children.Add(cargo);
Stats.SetCargoCount();
Stats.SetTonnageCount(vTonnage);
if (vCargoHeavy)
{
Stats.SetHLCount();
}
if (!(vCargoDG==null))
{
Stats.SetDGCount();
}
}
code here

How to Pass and Set values to Usercontrol in wpf

How to Pass and Set values to Usercontrol.
Here I have two question regarding a WPF project.
Overview:
In this project, I'm trying to use a Usercontrol as a resources on a canvas and connecting them with each other by LineGeometry. 'MyThumb' class i'm using to make control dragable and control set values.
where i'm using overrided 'OnApplyTemplate' method to pass values on the Usercontrol. To set value i'm using 'Template.FindName'. Here it is returning null.
Now the question is 'how to set values on usercontrol' in the code below:
Another question is related to line color. In the project i'm using LineGeometry to display lines between usercontrols but not able to change line colors.. want to set different lines with different color.
Here the question is how to change color of existing LineGeometry.
Please have a look to the codes i'm using:
Mythumb.cs
public class MyThumb : Thumb
{
#region Properties
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(string), typeof(MyThumb), new UIPropertyMetadata(""));
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(string), typeof(MyThumb), new UIPropertyMetadata(""));
// This property will hanlde the content of the textblock element taken from control template
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
// This property will handle the content of the image element taken from control template
public string ImageSource
{
get { return (string)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public Point MyLocation
{
get
{
Point nm = new Point(Canvas.GetLeft(this), Canvas.GetTop(this));
return nm;
}
}
public List<LineGeometry> EndLines { get; private set; }
public List<LineGeometry> StartLines { get; private set; }
#endregion
#region Constructors
public MyThumb()
: base()
{
StartLines = new List<LineGeometry>();
EndLines = new List<LineGeometry>();
}
public MyThumb(string title, string imageSource, Point position)
: this()
{
//Setting ControlTemplate as Usercontrol 'ContactNode'
ControlTemplate templatex = new ControlTemplate();
var fec = new FrameworkElementFactory(typeof(ContactNode));
templatex.VisualTree = fec;
this.Template = templatex;
this.Title = (title != null) ? title : string.Empty;
this.ImageSource = (imageSource != null) ? imageSource : string.Empty;
this.SetPosition(position);
}
public MyThumb( string title, string imageSource, Point position, DragDeltaEventHandler dragDelta)
: this(title, imageSource, position)
{
this.DragDelta += dragDelta;
}
#endregion
// Helper method for setting the position of our thumb
public void SetPosition(Point value)
{
Canvas.SetLeft(this, value.X);
Canvas.SetTop(this, value.Y);
}
#region Linking logic
// This method establishes a link between current thumb and specified thumb.
// Returns a line geometry with updated positions to be processed outside.
public LineGeometry LinkTo(MyThumb target)
{
// Create new line geometry
LineGeometry line = new LineGeometry();
// Save as starting line for current thumb
this.StartLines.Add(line);
// Save as ending line for target thumb
target.EndLines.Add(line);
// Ensure both tumbs the latest layout
this.UpdateLayout();
target.UpdateLayout();
// Update line position
line.StartPoint = new Point(Canvas.GetLeft(this) + this.ActualWidth / 2, Canvas.GetTop(this) + this.ActualHeight / 2);
line.EndPoint = new Point(Canvas.GetLeft(target) + target.ActualWidth / 2, Canvas.GetTop(target) + target.ActualHeight / 2);
// return line for further processing
return line;
}
// This method establishes a link between current thumb and target thumb using a predefined line geometry
// Note: this is commonly to be used for drawing links with mouse when the line object is predefined outside this class
public bool LinkTo(MyThumb target, LineGeometry line)
{
// Save as starting line for current thumb
this.StartLines.Add(line);
// Save as ending line for target thumb
target.EndLines.Add(line);
// Ensure both tumbs the latest layout
this.UpdateLayout();
target.UpdateLayout();
// Update line position
line.StartPoint = new Point(Canvas.GetLeft(this) + this.ActualWidth / 2, Canvas.GetTop(this) + this.ActualHeight / 2);
line.EndPoint = new Point(Canvas.GetLeft(target) + target.ActualWidth / 2, Canvas.GetTop(target) + target.ActualHeight / 2);
return true;
}
#endregion
// This method updates all the starting and ending lines assigned for the given thumb
// according to the latest known thumb position on the canvas
public void UpdateLinks()
{
double left = Canvas.GetLeft(this);
double top = Canvas.GetTop(this);
for (int i = 0; i < this.StartLines.Count; i++)
this.StartLines[i].StartPoint = new Point(left + this.ActualWidth / 2, top + this.ActualHeight / 2);
for (int i = 0; i < this.EndLines.Count; i++)
this.EndLines[i].EndPoint = new Point(left + this.ActualWidth / 2, top + this.ActualHeight / 2);
}
// Upon applying template we apply the "Title" and "ImageSource" properties to the template elements.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Access the textblock element of template and assign it if Title property defined
if (this.Title != string.Empty)
{
TextBlock txt = this.Template.FindName("tplTextBlock", this) as TextBlock;
if (txt != null) //getting null here unable to find element..
txt.Text = Title;
}
// Access the image element of our custom template and assign it if ImageSource property defined
if (this.ImageSource != string.Empty)
{
Image img = this.Template.FindName("tplImage", this) as Image;
if (img != null)
img.Source = new BitmapImage(new Uri(this.ImageSource, UriKind.Relative));
}
}
}
Window1.xaml.cs
public partial class Window1 : Window
{
// flag for enabling "New thumb" mode
bool isAddNewAction = false;
// flag for enabling "New link" mode
bool isAddNewLink = false;
// flag that indicates that the link drawing with a mouse started
bool isLinkStarted = false;
// variable to hold the thumb drawing started from
MyThumb linkedThumb;
// Line drawn by the mouse before connection established
LineGeometry link;
public Window1()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Point pp = new Point(myCanvas.ActualWidth/2,myCanvas.ActualHeight/2);
AdNewNode(pp, "ACTION", "/Images/gear_connection.png");
this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(Window1_PreviewMouseLeftButtonDown);
this.PreviewMouseMove += new MouseEventHandler(Window1_PreviewMouseMove);
this.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(Window1_PreviewMouseLeftButtonUp);
}
// Event hanlder for dragging functionality support
private void onDragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
// Exit dragging operation during adding new link
if (isAddNewLink) return;
MyThumb thumb = e.Source as MyThumb;
Canvas.SetLeft(thumb, Canvas.GetLeft(thumb) + e.HorizontalChange);
Canvas.SetTop(thumb, Canvas.GetTop(thumb) + e.VerticalChange);
// Update links' layouts for active thumb
thumb.UpdateLinks();
}
// Event handler for creating new thumb element by left mouse click
// and visually connecting it to the myThumb2 element
void AdNewNode(Point nPosition,string nTitle, string nImage)
{
MyThumb newThumb = new MyThumb(
nTitle,
nImage,
nPosition,
onDragDelta);
myCanvas.Children.Add(newThumb);
}
void Window1_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// If adding new action...
if (isAddNewAction)
{
AdNewNode(e.GetPosition(this), "ACTION", "/Images/gear_connection.png");
// resume common layout for application
isAddNewAction = false;
Mouse.OverrideCursor = null;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = true;
e.Handled = true;
}
// Is adding new link and a thumb object is clicked...
if (isAddNewLink && e.Source.GetType() == typeof(MyThumb))
{
if (!isLinkStarted)
{
if (link == null || link.EndPoint != link.StartPoint)
{
Point position = e.GetPosition(this);
link = new LineGeometry(position, position);
//nodepath=new Path();
//nodepath.Stroke = Brushes.Pink;
//nodepath.Data = link; // Here line color is getting change but connectivity getting lost....
connectors.Children.Add(link);
//myCanvas.Children.Add(nodepath);
isLinkStarted = true;
linkedThumb = e.Source as MyThumb;
e.Handled = true;
}
}
}
}
// Handles the mouse move event when dragging/drawing the new connection link
void Window1_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (isAddNewLink && isLinkStarted)
{
// Set the new link end point to current mouse position
link.EndPoint = e.GetPosition(this);
e.Handled = true;
}
}
// Handles the mouse up event applying the new connection link or resetting it
void Window1_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// If "Add link" mode enabled and line drawing started (line placed to canvas)
if (isAddNewLink && isLinkStarted)
{
// declare the linking state
bool linked = false;
// We released the button on MyThumb object
if (e.Source.GetType() == typeof(MyThumb))
{
MyThumb targetThumb = e.Source as MyThumb;
// define the final endpoint of line
link.EndPoint = e.GetPosition(this);
// if any line was drawn (avoid just clicking on the thumb)
if (link.EndPoint != link.StartPoint && linkedThumb != targetThumb)
{
// establish connection
linkedThumb.LinkTo(targetThumb, link);
// set linked state to true
//nodepath = new Path();
//nodepath.Stroke = Brushes.Pink;
//nodepath.Data = link;
linked = true;
}
}
// if we didn't manage to approve the linking state
// button is not released on MyThumb object or double-clicking was performed
if (!linked)
{
// remove line from the canvas
//connectors.Children.Remove(link);
// clear the link variable
link = null;
}
// exit link drawing mode
isLinkStarted = isAddNewLink = false;
// configure GUI
btnNewAction.IsEnabled = btnNewLink.IsEnabled = true;
Mouse.OverrideCursor = null;
e.Handled = true;
}
//this.Title = "Links established: " + connectors.Children.Count.ToString();
}
// Event handler for enabling new thumb creation by left mouse button click
private void btnNewAction_Click(object sender, RoutedEventArgs e)
{
isAddNewAction = true;
Mouse.OverrideCursor = Cursors.SizeAll;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = false;
}
private void btnNewLink_Click(object sender, RoutedEventArgs e)
{
isAddNewLink = true;
Mouse.OverrideCursor = Cursors.Cross;
btnNewAction.IsEnabled = btnNewLink.IsEnabled = false;
}
}
Please try to correct me where i'm doing wrong in this code.

Prevent ToolStripMenuItems from jumping to second screen

I have an application that is mostly operated through NotifyIcon's ContextMenuStrip
There are multiple levels of ToolStripMenuItems and the user can go through them.
The problem is, that when the user has two screen, the MenuItems jump to second screen when no space is available. like so:
How can I force them to stay on the same screen? I've tried to search through the web but couldn't find an appropriate answer.
Here is a sample piece of code i'm using to test this senario:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var resources = new ComponentResourceManager(typeof(Form1));
var notifyIcon1 = new NotifyIcon(components);
var contextMenuStrip1 = new ContextMenuStrip(components);
var level1ToolStripMenuItem = new ToolStripMenuItem("level 1 drop down");
var level2ToolStripMenuItem = new ToolStripMenuItem("level 2 drop down");
var level3ToolStripMenuItem = new ToolStripMenuItem("level 3 drop down");
notifyIcon1.ContextMenuStrip = contextMenuStrip1;
notifyIcon1.Icon = ((Icon)(resources.GetObject("notifyIcon1.Icon")));
notifyIcon1.Visible = true;
level2ToolStripMenuItem.DropDownItems.Add(level3ToolStripMenuItem);
level1ToolStripMenuItem.DropDownItems.Add(level2ToolStripMenuItem);
contextMenuStrip1.Items.Add(level1ToolStripMenuItem);
}
}
It is not easy, but you can write code in the DropDownOpening event to look at where the menu is at (its bounds), the current screen, and then set the DropDownDirection of the ToolStripMenuItem:
private void submenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem menuItem = sender as ToolStripMenuItem;
if (menuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
// Current bounds of the current monitor
Rectangle Bounds = menuItem.GetCurrentParent().Bounds;
Screen CurrentScreen = Screen.FromPoint(Bounds.Location);
// Look how big our children are:
int MaxWidth = 0;
foreach (ToolStripMenuItem subitem in menuItem.DropDownItems)
{
MaxWidth = Math.Max(subitem.Width, MaxWidth);
}
MaxWidth += 10; // Add a little wiggle room
int FarRight = Bounds.Right + MaxWidth;
int CurrentMonitorRight = CurrentScreen.Bounds.Right;
if (FarRight > CurrentMonitorRight)
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
menuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
Also, make sure you have the DropDownOpening event hooked up (you would really need to add this to every menu item):
level1ToolStripMenuItem += submenu_DropDownOpening;
I have solved it this way:
For the ContextMenuStrip itself to open on a desired screen, I created a ContextMenuStripEx with the following methods:
protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified)
{
Rectangle dropDownBounds = new Rectangle(x, y, width, height);
dropDownBounds = ConstrainToBounds(Screen.FromPoint(dropDownBounds.Location).Bounds, dropDownBounds);
base.SetBoundsCore(dropDownBounds.X, dropDownBounds.Y, dropDownBounds.Width, dropDownBounds.Height, specified);
}
internal static Rectangle ConstrainToBounds(Rectangle constrainingBounds, Rectangle bounds)
{
if (!constrainingBounds.Contains(bounds))
{
bounds.Size = new Size(Math.Min(constrainingBounds.Width - 2, bounds.Width), Math.Min(constrainingBounds.Height - 2, bounds.Height));
if (bounds.Right > constrainingBounds.Right)
{
bounds.X = constrainingBounds.Right - bounds.Width;
}
else if (bounds.Left < constrainingBounds.Left)
{
bounds.X = constrainingBounds.Left;
}
if (bounds.Bottom > constrainingBounds.Bottom)
{
bounds.Y = constrainingBounds.Bottom - 1 - bounds.Height;
}
else if (bounds.Top < constrainingBounds.Top)
{
bounds.Y = constrainingBounds.Top;
}
}
return bounds;
}
(ConstrainToBounds method is taken from the base class ToolStripDropDown via Reflector)
for the nested MenuItems to open on the same screen as ContextMenuStrip, I created a ToolStripMenuItemEx (which derives from ToolStripMenuItem). In my case it looks like this:
private ToolStripDropDownDirection? originalToolStripDropDownDirection;
protected override void OnDropDownShow(EventArgs e)
{
base.OnDropDownShow(e);
if (!Screen.FromControl(this.Owner).Equals(Screen.FromPoint(this.DropDownLocation)))
{
if (!originalToolStripDropDownDirection.HasValue)
originalToolStripDropDownDirection = this.DropDownDirection;
this.DropDownDirection = originalToolStripDropDownDirection.Value == ToolStripDropDownDirection.Left ? ToolStripDropDownDirection.Right : ToolStripDropDownDirection.Left;
}
}
The code of #David does not fix if the menu is opened in the left side of second screen. I have improved that code to work on all screen corner.
private void subMenu_DropDownOpening(object sender, EventArgs e)
{
ToolStripMenuItem mnuItem = sender as ToolStripMenuItem;
if (mnuItem.HasDropDownItems == false)
{
return; // not a drop down item
}
//get position of current menu item
var pos = new Point(mnuItem.GetCurrentParent().Left, mnuItem.GetCurrentParent().Top);
// Current bounds of the current monitor
Rectangle bounds = Screen.GetWorkingArea(pos);
Screen currentScreen = Screen.FromPoint(pos);
// Find the width of sub-menu
int maxWidth = 0;
foreach (var subItem in mnuItem.DropDownItems)
{
if (subItem.GetType() == typeof(ToolStripMenuItem))
{
var mnu = (ToolStripMenuItem) subItem;
maxWidth = Math.Max(mnu.Width, maxWidth);
}
}
maxWidth += 10; // Add a little wiggle room
int farRight = pos.X + mnuMain.Width + maxWidth;
int farLeft = pos.X - maxWidth;
//get left and right distance to compare
int leftGap = farLeft - currentScreen.Bounds.Left;
int rightGap = currentScreen.Bounds.Right - farRight;
if (leftGap >= rightGap)
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Left;
}
else
{
mnuItem.DropDownDirection = ToolStripDropDownDirection.Right;
}
}
I did not try the solution by tombam. But since the others didn't seem to work, I came up with this simple solution:
private void MenuDropDownOpening(object sender, EventArgs e)
{
var menuItem = sender as ToolStripDropDownButton;
if (menuItem == null || menuItem.HasDropDownItems == false)
return; // not a drop down item
// Current bounds of the current monitor
var upperRightCornerOfMenuInScreenCoordinates = menuItem.GetCurrentParent().PointToScreen(new Point(menuItem.Bounds.Right, menuItem.Bounds.Top));
var currentScreen = Screen.FromPoint(upperRightCornerOfMenuInScreenCoordinates);
// Get width of widest child item (skip separators!)
var maxWidth = menuItem.DropDownItems.OfType<ToolStripMenuItem>().Select(m => m.Width).Max();
var farRight = upperRightCornerOfMenuInScreenCoordinates.X + maxWidth;
var currentMonitorRight = currentScreen.Bounds.Right;
menuItem.DropDownDirection = farRight > currentMonitorRight ? ToolStripDropDownDirection.Left :
ToolStripDropDownDirection.Right;
}
Note that in my world, I was not concerned about multiple levels of cascading menus (as in the OP), so I did not test my solution in that scenario. But this works correctly for a single ToolStripDropDownButton on a ToolStrip.

Painting a TextBox

I'm in need of a way to make TextBox appear like a parallelogram but i can't figure out how to do so. I currently have this code:
private void IOBox_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Point cursor = PointToClient(Cursor.Position);
Point[] points = { cursor, new Point(cursor.X + 50, cursor.Y), new Point(cursor.X + 30, cursor.Y - 20),
new Point(cursor.X - 20, cursor.Y - 20) };
Pen pen = new Pen(SystemColors.MenuHighlight, 2);
g.DrawLines(pen, points);
}
But apparently it's not working. Either i misplaced/misused it or i'm not doing something right.
This is the method that i use to add it.
int IOCounter = 0;
private void inputOutput_Click(object sender, EventArgs e)
{
IOBox box = new IOBox();
box.Name = "IOBox" + IOCounter;
IOCounter++;
box.Location = PointToClient(Cursor.Position);
this.Controls.Add(box);
}
Any idea how i can fix it? IOBox is a UserControl made by me which contains a TextBox. Is that rightful to do?
If its possible, you should make your application using WPF. WPF is designed to do exactly what you are trying to do.
However, it can be done in WinForms, though not easily. You will need to make a new class that inherits the TextBox WinForm control. Here is an example that makes a TextBox look like a circle:
public class MyTextBox : TextBox
{
public MyTextBox() : base()
{
SetStyle(ControlStyles.UserPaint, true);
Multiline = true;
Width = 130;
Height = 119;
}
public override sealed bool Multiline
{
get { return base.Multiline; }
set { base.Multiline = value; }
}
protected override void OnPaintBackground(PaintEventArgs e)
{
var buttonPath = new System.Drawing.Drawing2D.GraphicsPath();
var newRectangle = ClientRectangle;
newRectangle.Inflate(-10, -10);
e.Graphics.DrawEllipse(System.Drawing.Pens.Black, newRectangle);
newRectangle.Inflate(1, 1);
buttonPath.AddEllipse(newRectangle);
Region = new System.Drawing.Region(buttonPath);
base.OnPaintBackground(e);
}
}
Keep in mind that you will still have to do other things, such as clipping the text, etc. But this should get you started.

Handle scrolling of a WinForms control manually

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;
}
}
}

Categories