I have written a C# wpf program that deals with curves on a canvas.
So I load a curve (a sequence of points in a polylinesegment) and then I operate various operations on it. Each curve is put on the screen through mouse interaction and that works fine. Each curve then comes with a textblock in its center which gives out some information.
So the problem comes when I want to mouse-move the shape.
I first select the shape with the mouse (that works) and then I stick it to the cursor through the OnMouseMove event. Eventually I put it down on the with the OnMouseLeftButtonDown event.
So in short the OnMouseLeftButtonDown always works fine except when I have to move the shape AND the label. In that case I have to press several times (randomly) to fire the event.
I have then searched the part which cause the problem and that is when I move the label.
private void UpdateLabel(int index, PathInfo piToBeAdded)
{
plotCanvas.Children.Remove(names[index]);
TextBlock text = new TextBlock();
text.TextAlignment = TextAlignment.Left;
text.FontSize = 12;
text.Inlines.Add(new Run("(" + (GetPathsIndexFromId(piToBeAdded.ID) + 1) + ")ID:" + piToBeAdded.ID + " " + piToBeAdded.Name) { FontWeight = FontWeights.Bold });
Canvas.SetLeft(text, piToBeAdded.Center.X);<-----those cause the problem
Canvas.SetTop(text, piToBeAdded.Center.Y);<------those cause the problem
text.ReleaseMouseCapture();
names[index] = text;
plotCanvas.Children.Add(text);
}
NB: pathinfo is just a class storing some information among which also coordinates
Specifically it's just the Canvas.SetLeft and Canvas.SetTop that causes the OnMouseLeftButtonDown not to fire properly. I I take them off the label goes in 0,0 and the event
But what's wrong with those instructions? How can I make the OnLeftButtonDownEvent to work properly?
I hope that I have described the problem properly I've tried to put all related information.
Thanx in advance
Patrick
Typically it is better to use the MouseButtonUp event to release mouse capture as the mouse button has definitely been released at that point and the movement has stopped (which is what you are capturing).
Your issue with Canvas.SetLeft is because it can only be called on a child of the Canvas object and you are only adding text to the Children collection after calling Canvas.SetLeft.
Edit:
In answer to your comment, the Canvas.SetLeft can only be called on an existing child of the Canvas, so call Add before calling Canvas.SetLeft.
private void UpdateLabel(int index, PathInfo piToBeAdded)
{
plotCanvas.Children.Remove(names[index]);
TextBlock text = new TextBlock();
text.TextAlignment = TextAlignment.Left;
text.FontSize = 12;
text.Inlines.Add(new Run("(" + (GetPathsIndexFromId(piToBeAdded.ID) + 1) + ")ID:" + piToBeAdded.ID + " " + piToBeAdded.Name) { FontWeight = FontWeights.Bold });
plotCanvas.Children.Add(text); // <---- moved this up
Canvas.SetLeft(text, piToBeAdded.Center.X);
Canvas.SetTop(text, piToBeAdded.Center.Y);
names[index] = text;
}
On the second part of your comment, I would suggest that you attach handlers to the different draggable items and set flags for the appropriate "mode" of operation that you are working in i.e. bool variables for ShapeDragInProgress and LabelDragInProgress. That way you can then conditionally perform the correct release capture procedure based on the ButtonUp event.
Toadflakz thanx in any case I am not sure if my solution has got to do with what you suggest. In case I will give you the flag.
I noticed that the problem is related with the fact that when moving shape+textblock the mouse point is EXACTLY on the center of the shape (I did that on purpose) and therefore EXACTLY on the textblock. Therefore when I click i don't click on the canvas but on the label. This is why the canvas is not firing. I suppose that the label is firing. So in short I just moved the label some pixels away and that did the trick!!
Related
I can't find tools or properties to place a label or a button exactly in the middle of the Form. For example, on the X axis. VS 2015.
Design time :
In my VisualStudio2010 I have these 2 buttons to center horizontally and vertically:
Its located in the toolbar "Layout". If it isn't, you can add them by clicking the small button to the right. It is also in the Format menu.
To keep centered at Runtime: Turn off all anchoring.
Note:This will keep the control at its relative position as long as it doesn't change it Size. If it does, like autosize Labels are prone to, you will have to code the Resize event. Examples are here
For controls that may change in size, you need to catch the Resize event.
In my case I have a Panel, representing a page, inside another Panel which is the workspace. The workspace is set to autoscroll. In this scenario, it's important that the control is only centered when smaller than the container.
Whenever the form changes size or when I change the content, I call this function:
private void resetPagePos()
{
int wWS = pnlWorkspace.Width;
int hWS = pnlWorkspace.Height;
int wPage = pnlPage.Width;
int hPage = pnlPage.Height;
pnlPage.Location = new Point(Math.Max(0, (wWS - wPage) / 2), pnlPage.Top = Math.Max(0, (hWS - hPage) / 2));
}
The use of Math.Max(0, ...) makes sure that if the item doesn't fit, and the scrollbars are activates, then our page scrolls correctly. If the Left or Top are set to a negative number, you would get unwanted side-effects.
I just wanted to set the value of the scrollbar of my scrollview object to 0 every time a new text object is created. This way the newest object would always remain at the bottom without needing to scroll down (think of the chat of a video game for example). My problem is that for some reason the program is setting the value to 0 and then creating the text object, so it keeps the 2nd newest object at the bottom of the scrollview.
My code:
//Creates the Text objects
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
ScrollbarVertical.value = 0;
return messagePrefab;
}
In the code I am setting the value at the end of the function, but still sets the value of the scrollbar to 0 (properly moving the scrollview to the bottom) before the object is created.
https://gyazo.com/897982521f13d7792ec26540490a40c0
In the Gyazo picture you can see how it doesn't scroll all the way down.
I have tried using a coroutine and waitForEndFrame aswell as waitforseconds(1), but none seem to work.
Edit: when loading up Unity and sending new messages to the scrollview, I see the scrollbar go all the way down and then really quickly move up just a bit hiding the new text object.
What you can do is create a new Scroll View and follow these steps:
The result is from calling GenerateTextObject(Time.time.ToString()); every second.
private GameObject GenerateTextObject(string content)
{
//Creates the Text objects
GameObject messagePrefab = Instantiate(MessagePrefab);
Text textComponent = messagePrefab.GetComponent<Text>();
//Sets the content of the text and parent
textComponent.text = content;
messagePrefab.transform.SetParent(ContentPanel.transform, false);
//Force reset to the bottom on each message
//ScrollbarVertical.value = 0;
return messagePrefab;
}
I have not come up with a proper fix for this problem (I honestly do not know the cause of problem), but I have put together a little work-around for this situation.
To do this I simply created a small function:
public void Testing()
{
if (testing == true)
{
ScrollbarVertical.value = 0;
testing = false;
}
}
This would be attached to the built-in functionality of scrollbars "On Value Changed (Single)". In order to allow this to work, I just change the bool "testing" to true after creating a text object on GenerateTextObject(string content).
This fix is really ugly, and probably would cause more problems in the near future, but for now, it is a quick and dirty solution for this problem.
Sorry to necro this, but I had a similar problem and googling couldn't find a good answer. So I'm posting the solution that I found.
I tried to change the scrollbar value to 1 or 0, depending on the direction, so that everytime I open a UI panel, it'll start at the top. But no matter what I did, nothing seemed to work.
So my solution is to change the Y position of the content instead of the scrollbar value.
My scrollbar direction is set at bottom to top. Then I take the sizeDelta of the content, divide it by 2, (since I need the value of 1 for the scrollbar.value). I then multiplied it by -1 (if I dont, it'll start at the bottom). Then I set that value as the Y position of the content.
So far it works like a charm everytime I open my UI panel.
Hope this helps anyone looking for a solution.
G'day,
I am attempting to simulate the old XBox 360 GUI with the sliding tabs (Remember, you'd press left or right and the content would slide in depending on the tab?) Anyways, at the moment, I have this working well, however I cannot get the "animation" working.
When the user presses left arrow or right arrow, my OpenWindow(int iIndex) method will be called, where iIndex is the index to the next or previous "window" to be slid in. (Not a window... each "Window" is a struct with a parent grid control containing a button and a smaller grid control that contains the windows contents.)
Now, my problem lies with resizing the parent grid control. When it is slid in, it is resized by calling mygrid.Width += 1; That works, but I don't see it happen over a determined period of time, it just lags a bit and then resizes to the required width. Whereas if I call this.Width += 1 in the same method, (this being the main program window) the window resizes how I want the grid control to resize. I've tried UpdateLayout() but to no avail. This tells me my timing is okay.
If anyone could be of assistance, it would be greatly appreciated.
Here is my OpenWindow method...
public void OpenWindow(int iIndex)
{
int iInterval = 1;
for (int i = (int)myDict[iIndex].Shell.Width; i < (int)stack_outter.Width; i += iInterval)
{
myDict[iIndex].Shell.Width += 1;
myDict[iIndex].Shell.UpdateLayout();
System.Threading.Thread.Sleep(1);
}
myDict[iIndex].Shell.Width = stack_outter.Width - (BUTTON_WIDTH * (myDict.Count - 1));
}
myDict is a Dictionary, Shell is the grid that I am attempting to animate when resizing. Sorry about the code, it's messy, my code is always hacked when I am trying to get stuff working :)
Thanks,
Ash
Neried Web Solutions
Your OpenWindow method is happening on the Dispatcher thread. That's also the thread responsible for rendering, so as long as your OpenWindow method doesn't return, nothing gets rendered.
The proper way to fix this would be to animate the Width property. I don't have any experience in starting animations from code (I've only used them in the past for things like a fade-in button highlight on mouse over, which is more easily done from WPF), but I took a quick read-through this page, Animation Overview on MSDN, and I think you'll want something like this:
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = myDict[iIndex].Shell.Width;
myDoubleAnimation.To = stack_outter.Width;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(0.5));
myDoubleAnimation.AutoReverse = false;
myDoubleAnimation.RepeatBehavior = new RepeatBehavior(1.0);
myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
Storyboard.SetTarget(myDoubleAnimation, myDict[iIndex].Shell);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(FrameworkElement.WidthProperty));
myStoryboard.Begin(myDict[iIndex].Shell);
happy holidays!
i have a tablelayoutpanel (10x10). within each cell i have a picturebox which are disabled (enabled = false).
i am trapping mouse move over the table to catch mouse movement. here is the code:
private void tableLayoutPanelTest_MouseMove(object sender, MouseEventArgs e)
{
if (!placeShip)
{
c = tableLayoutPanelTest.GetControlFromPosition(homeLastPosition.Column, homeLastPosition.Row);
if (c.GetType() == typeof(PictureBox))
{
PictureBox hover = new PictureBox();
hover = (PictureBox)(c);
hover.Image = Properties.Resources.water;
}
Point p = tableLayoutPanelTest.PointToClient(Control.MousePosition);
Control picControl = tableLayoutPanelTest.GetChildAtPoint(p);
if (picControl != null)
{
TableLayoutPanelCellPosition me = tableLayoutPanelTest.GetCellPosition(picControl);
if (picControl.GetType() == typeof(PictureBox))
{
PictureBox thisLocation = new PictureBox();
thisLocation = (PictureBox)(picControl);
thisLocation.Image = Properties.Resources.scan;
homeLastPosition = me;
}
}
}
toolTipApp.SetToolTip(tableLayoutPanelTest, tableLayoutPanelTest.GetCellPosition(c).ToString());
}
when i run this the tooTipApp starts consuming upto 56% of the CPU. so there is something wrong.
also the picturebox image changing code stops working for some reason.
any help is very welcome!
thank you.
A few thoughts:
You're creating another PictureBox called hover - why? This code doesn't seem to do anything and it's almost certainly going to slow the loop down. I think you meant to just declare hover and cast it from c, but you're actually creating a new PictureBox instance and just throwing it away.
You're also never disposing of hover, as far as I can tell - so you end up allocating tons of memory and window handles. In general you should avoid creating new objects at all inside a MouseMove handler (small ones like hit tests are sometimes OK). As with the previous point - you probably didn't mean to write the new PictureBox().
You use PointToClient(Control.MousePosition) when the MouseMove event already gives you the control-specific mouse position (e.X and e.Y). This is costing you more time than it should.
Probably the most important, you're invoking SetToolTip on every MouseMove. You should only be invoking this when the tooltip has actually changed. You need to set a flag on which cell or control the tooltip was last displayed for, check for changes, then call SetToolTip.
You will get a lot of performance back if you avoid setting the tooltip text when it hasn't changed.
Other than that I want to echo the comment. This is a lot of processing for a mouse handler.
You should be trying to do an early test to see if the mouse is still over the same thing it was on the last move, and skipping most of the code in that case.
I am responding to MouseLeftButtonDown events on elements added to a WPF canvas. It all works fine when clicked (i.e. the eventhandler fires off correctly), but it requires too much precision from the mouse pointer. You have to be perfectly on top of the circle to make it work. I need it to be a little more forgiving; maybe at least 1 or 2 pixles forgiving. The elements on the canvas are nice big circles (about the size of a quarter on the screen), so the circles themselves are not too small, but the StrokeWidth of each one is 1, so it is a thin line.
You can see a screenshot here: http://twitpic.com/1f2ci/full
Most graphics app aren't this picky about the mouse picking, so I want to give the user a familiar experience.
How can I make it a little more forgiving.
You can hook up to the MouseLeftButtonDown event of your root layout object instead, and check which elements is in range of a click by doing this:
List<UIElement> hits = System.Windows.Media.VisualTreeHelper.FindElementsInHostCoordinates(Point, yourLayoutRootElement) as List<UIElement>;
http://msdn.microsoft.com/en-us/library/cc838402(VS.95).aspx
For the Point parameter, you can use the MouseEventArgs parameter e, and call its GetPosition method like this:
Point p = e.GetPosition(null)
I can't remember whether to use HitTest instead of the FindElementsInHostCoordinates. Try both.
http://msdn.microsoft.com/en-us/library/ms608752.aspx
You could create 4 Point objects from the mouse position to create a fake tolerence effect, and call either FindElementsInHostCoordinates or HitTest for all 4 points.
You might want to try to fill the circle with the Transparent colour to make the whole circle clickable...
If that fails, you can also draw helper circles on the same location as the other circles. Make the circle foreground colour Transparent, and make the thickness of the brush a few pixels wider for a more acceptable clickable region around the circle..
Hope this helps!
I think I've done it (with you help to get me started)...
First, I've moved the move event handling to the Canvas instead of each Ellipse. That's good and bad, from an OOP standpoint. At least when the mouse event handling is a responsibility of the HolePattern to set it on up each Hole (the ellipse that is the visual of the Hole), it is abstracted away so that any consumer of my HolePattern will get this functioanality automactically. However, by moving it to the main UI code, I now am dealing with my canvas mouse event at a higher level. But that's not all bad either. We could discuss this part for days.
The point is, I have designed a way to create a "margin of error" when picking something on a canvas with a mouse, and then reading the Hole that the selected Ellipse belongs to, and then I can read the HolePattern that the Hole belongs to, and my entire UI (ListView, textboxes, gridview fo coordinates) are ALL updated by the existing XAML binding, and the Canvas is updated with one call to an existing method to regenerate the canvas.
To be honest, I can't believe I've figured all this out (with your help and others too, of course). It is such a cool feeling to have the vision of this this and see it come to be.
Check out the main code here:
void canvas1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
int ClickMargin = 2;
Point ClickedPoint = e.GetPosition(canvas1);
Point p1 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y - ClickMargin);
Point p2 = new Point(ClickedPoint.X - ClickMargin, ClickedPoint.Y + ClickMargin);
Point p3 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y + ClickMargin);
Point p4 = new Point(ClickedPoint.X + ClickMargin, ClickedPoint.Y - ClickMargin);
var PointPickList = new Collection<Point>();
PointPickList.Add(ClickedPoint);
PointPickList.Add(p1);
PointPickList.Add(p2);
PointPickList.Add(p3);
PointPickList.Add(p4);
foreach (Point p in PointPickList)
{
HitTestResult SelectedCanvasItem = System.Windows.Media.VisualTreeHelper.HitTest(canvas1, p);
if (SelectedCanvasItem.VisualHit.GetType() == typeof(Ellipse))
{
var SelectedEllipseTag = SelectedCanvasItem.VisualHit.GetValue(Ellipse.TagProperty);
if (SelectedEllipseTag!=null && SelectedEllipseTag.GetType().BaseType == typeof(Hole))
{
Hole SelectedHole = (Hole)SelectedEllipseTag;
SetActivePattern(SelectedHole.ParentPattern);
SelectedHole.ParentPattern.CurrentHole = SelectedHole;
}
}
}
}
Just Increase Stroke ThickNess of the Ellipse so that it is adjustable
thus the MouseLeftButtonDown event works
Example:
In Ellipse tag:
Ellipse
Canvas.Left="10" Canvas.Top="133" Height="24" Name="ellipse1" Width="23" Stroke="Red" MouseLeftButtonDown="ellipse1_MouseLeftButtonDown" ToolTip="Temp Close" StrokeEndLineCap="Flat" StrokeThickness="12"
private void ellipse1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Application curApp = Application.Current;
curApp.Shutdown();
}