How do I programmatically position a canvas in Silverlight? - c#

I'm using Silverlight 3/C#, and I'm trying to create some XAML objects programatically in response to a user clicking on a button.
However, I can't seem to position a Canvas object that I have created - when I call SetValue() on the new Canvas with the Canvas.LeftProperty value, the browser window clears to an empty screen.
I have a simple function that exhibits the problem (I started out with something more complex):
private Canvas MakeNewCanvas()
{
Canvas newCanvas = new Canvas();
newCanvas.Width = newCanvas.Height = 50;
newCanvas.SetValue(Canvas.LeftProperty, 10);
return newCanvas;
}
and a simple button click handler that calls this:
private void MyButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
myPage.Children.Add(MakeNewCanvas());
}
NB: myPage is a Canvas object defined in my app's MainPage XAML file.
I've traced this through in the VS debugger with the SL app executing in Firefox, and whenever it executes the SetValue() line, the browser goes white and starts trying to download data (according to the status bar). I tried calling SetValue() after I've put the Canvas in the XAML tree with something like:
myPage.Children.Add(newCanvas);
newCanvas.SetValue(Canvas.LeftProperty, 10);
but it makes no difference - as soon as the SetValue() call is hit, the browser goes white.
The Canvas class seems to have no direct method of setting Left/Top on itself, apart from the SetValue() function with dependency property enum values. There's also Canvas.SetLeft() and Canvas.SetTop() but I guess they're just shims onto SetValue(). Using them didn't help anyway.
If I don't call SetValue(), then my canvas appears (and child objects I've added to it) in the SL app as you might expect, in the very top left.
If I create and position a Canvas object in Expression Blend, then the XAML generated includes Canvas.Left and Canvas.Top property values on the Canvas itself, as I would expect, so it seems to me that what I'm trying in C# should work.
But I don't seem to be able to set the left/top values myself from C# without the SL in the browser going all weird.
Any ideas?
Edit: my approach is correct, but canvas coords need to be floating point, not integer - see accepted answer for details.

Trying your code, but with the debugger catching exceptions, I get:
DependencyProperty of type System.Double cannot be set on an object of type System.Int32.
which is a really stupid gotcha - SetValue only takes Object, so you're prone to a problem like this.
Try either:
newCanvas.SetValue(Canvas.LeftProperty, 10.0);
or
Canvas.SetLeft(newCanvas, 10);
and it will probably work.

The behaviour of attached properties can be a little confusing at first.
The properties Canvas.LeftProperty and Canvas.TopProperty are applied to child objects of a canvas. They are therefore only meaningful when the child object is placed on a Canvas. Its important to understand the in WPF/SL objects do not position themselves, its up to the containing panel to decide where to put them.
I suspect the myPage is not of the Canvas type, its probably a Grid, hence it would have no idea what to do with such properties even if it bothered to look for them (which it doesn't).
In order for you to specifically position you new Canvas you need to be adding it to a Canvas.

Related

How to show or hide cursor on WinForms MS chart, C#

I have to use System.Windows.Forms.DataVisualization.Charting.Chart in my project
I created hair cross pair of System.Windows.Forms.DataVisualization.Charting.Cursor() on a chart;
I would like to show or hide these CursorX, CursorY on chart if needed.
The idea is to toggle cursor on chart by DoubleClick event.
I cannot find any property (i.e. CursorX.Visible) or method (i.e. CursorX.hide()) to do it.
To hide, I tried to copy cursor object to global private cursor object and dispose cursor object from chart.ChartArea; to show - [re]create cursor object again from global object.
But it caused more troubles because now I need to check everywhere if cursor object exists
Anybody knows a better way of hiding cursor?
P.S. I also used before National Instruments Measurements Studio chart - that is designed mach better, everything is thought out and much easier...
I found a way:
To make cursor "disappear", set ChartArea[0].CursorX.LineDashStyle to "NotSet"
To [re]appear, set this property to anything else from the choices: "Solid, Dot, Dash, etc."
Example:
chartArea2.CursorX.LineDashStyle = System.Windows.Forms.DataVisualization.Charting.ChartDashStyle.Solid;

Get default TitleBar height programmatically before a titlebar is created?

I am creating a custom title bar for my uwp app. I want to match the height of the system bar.
I might be able to get that height be calling
CoreApplication.GetCurrentView().TitleBar.Height
But that depends on a lot of things. The title bar may not have been sized yet.
I've also seen a suggestion (from winforms) to look at the difference of the y coordinates of the window top and the content view top. But again that seems fishy. For one thing, once I've set ExtendViewIntoTitleBar to true, I don't think the method would work.
Is there reliable way to programmatically get the default height?
I know that this answer might not be useful to the person who initially asked, at this point of time, but I would still like to suggest the answer:
You can register a handler for when the size of title bar changes. (The docs mention size change, but it may only be the caption button offset and not height)
This piece of code works well for me, at least at the moment
//Put the below line in the Page initialization/OnNavigated function
var coreTitleBar = CoreApplication.GetCurrentView().TitleBar;
coreTitleBar.ExtendViewIntoTitleBar = true;
coreTitleBar.LayoutMetricsChanged += CoreTitleBar_LayoutMetricsChanged;
private void CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar sender, object args)
{
MyAppTitleBar.Height = sender.Height;
}
The above code calls your function (CoreTitleBar_LayoutMetricsChanged) automatically whenever the dimensions of the titleBar change (like change in DPI). Here, MyAppTitleBar is a Grid I made for my custom title bar.
Futher info can be found here

base.OnPaint order changes drawing location

I have a strange problem. I made my own user control deriving from UserControl. I override OnPaint. Now I draw something in OnPaint. Let's say at position 0, 0.
If I call base.OnPaint after my custom drawing everything is fine. But if I call base.OnPaint before the stuff I'm drawing, it seems to ignore the containing control and the location is relative to the form instead of relative to the client area of the parent control. So when I draw at position (0, 0) it will effectively be drawn at negative x and y and I will only see a part of it. The base.OnPaint is UserControl.OnPaint. So I don't call my code there.
Here is an example:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var rect = new System.Drawing.Rectangle(this.ClientSize.Width - 16,
this.ClientSize.Height - 16, 16, 16);
e.Graphics.FillRectangle(new System.Drawing.SolidBrush(System.Drawing.Color.Red), rect);
//base.OnPaint(e);
}
In this case the red rectangle is displayed somewhere inside the client area but not at the lower right corner. If I uncomment the last line and comment the first line the red rectangle is displayed at the lower right corner as expected.
I don't get it. I did this many times and it always worked. So I tried to find any differences. The only I found is that I don't add my control in the designer but add it programmatically to another control with theContainingControl.Controls.Add(myMessedUpControl);.
This also happens for every parent-child-level I add. So if I create another control (another class) and also override OnPaint the behavior is the same if I add it to another user control.
Does anyone had this behavior before? How can I fix this? The problem is that I want to call base.OnPaint first and also everyone suggest this. But as I said I can't without messing the coordinates up.
One note: The coordinates are really 0, 0 in the debugger at the draw calls like DrawLine, DrawImage oder DrawString. But the result is displayed at negative coordinates (relative to the client area). It looks like the client coordinates are interpreted as client coordinates of the form. But I don't know why.
Found the problem
In my project there is a graphical overlay class which connects Paint event handlers to all controls in my form (the whole hierarchy). In this handler a transformation is performed. This graphical overlay kept me sleepless so many times. I guess I will remove it.
The Graphics object has a lot of mutable state. The order of operations matters if you mess with this mutable state - for example, you can use the Transform matrix to change the offset of everything rendered on the surface.
It sounds like your ascendant changes one of those during its own OnPaint handler without resetting it back. Try doing a e.Graphics.ResetTransform(); before you start your own painting. Make sure all the other state is also the way you want it (clip, DPI, ...).

How to block bing map pan to coordinate bound?

I'm using bing maps components in C# for WP7 with my custom tiles.
I override Microsoft.Phone.Controls.Maps.TileSource, all ok, I'm able to use my tiles.
But, because don't have entire world, I want to limit pan only in one city bound?
It's correct to use MapDragEventArgs to check and block pan??
private void map_MapPan(object sender, MapDragEventArgs e)
{
Map m = (Map)sender;
// something...?
}
...or I need to override something else? In that case, what do I need to override?
thanks.
I have not worked with Bing Maps on Windows phone 7, so going solely by the documentation, I would do the following:
Always initialize the map such that its view bounding rectangle is within your defined tile area.
Bind to ViewChangeStart event. (or ViewChangeOnFrame, you will have to experiment and figure out which to use)
Inside handler for ViewChangeStart, check if TargetBoundingRectangle properly falls outside of your tile coverage area.
If TargetBoundingRectangle falls outside of your coverage area, figure out a way to cancel the view change or manually set the bounding rectangle back the previous valid state.
Assuming everything works like I expect it to, this should limit the users map navigation to your limited areas and avoid the behavior of the view "Snapping" back to some other place after the user has navigated.
What you can easily do and would be the best approach is to use the ViewChangeEnd event and set back the map to your coverage area.
See the MSDN reference to do this:
http://msdn.microsoft.com/en-us/library/microsoft.phone.controls.maps.core.mapcore.viewchangeend(v=vs.92).aspx

Winforms: SuspendLayout/ResumeLayout is not enough?

I have a library of a few "custom controls". Essentially we have our own buttons, rounder corner panels, and a few groupboxes with some custom paint. Despite the "math" in the OnPaint methods, the controls are pretty standard. Most of the time, all we do is draw the rounded corners and add gradient to the background. We use GDI+ for all that.
These controls are ok (and very nice looking according to our customers), however and despite the DoubleBuffer, you can see some redrawing, especially when there are 20++ buttons (for example) on the same form. On form load you see the buttons drawing… which is annoying.
I'm pretty sure that our buttons are not the fastest thing on earth but my question is: if double buffer is "on", shouldn't all that redraw happen in background and the Windows subsystem should show the results "instantly" ?
On the other hand, if there's "complex" foreach loop that will create labels, add them to a panel (double buffered) and change their properties, if we suspendlayout of the panel before the loop and resume layout of the panel when the loop is over, shouldn't all these controls (labels and buttons) appear "almost instantly"? This doesn't happen like that, you can see the panel being filled.
Any idea why this is not happening? I know it's hard to evaluate without sample code but that's hard to replicate too. I could make a video with a camera, but trust me on this one, it's not fast :)
We've seen this problem too.
One way we've seen to "fix" it is to completely suspend drawing of the control until we're ready to go. To accomplish this, we send the WM_SETREDRAW message to the control:
// Note that WM_SetRedraw = 0XB
// Suspend drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero);
...
// Resume drawing.
UnsafeSharedNativeMethods.SendMessage(handle, WindowMessages.WM_SETREDRAW, new IntPtr(1), IntPtr.Zero);
One of the things you should look at is whether you have set BackColor=Transparent on any of the child controls of your panels. The BackColor=Transparent will significantly degrade rendering performance especially if parent panels are using gradients.
Windows Forms does not use real transparency, rather it is uses "fake" one. Each child control paint call generates paint call on parent so parent can paint its background over which the child control paints its content so it appears transparent.
So if you have 50 child controls that will generate additional 50 paint calls on parent control for background painting. And since gradients are generally slower you will see performance degradation.
Hope this helps.
I'll approach your problem from a performance angle.
foreach loop that will create labels,
add them to a panel (double buffered)
and change their properties
If that's the order things are done, there's room for improvement. First create all your labels, change their properties, and when they are all ready, add them to the panel: Panel.Controls.AddRange(Control[])
Most of the time, all we do is draw
the rounded corners and add gradient
to the background
Are you doing the same thing over and over again? How are your gradients generated? Writing an image can't be that slow. I once had to create a 1680x1050 gradient in-memory, and it was really fast, like, too fast for Stopwatch, so drawing a gradient can't be so hard.
My advice would be to try and cache some stuff. Open Paint, draw your corners and save to disk, or generate an image in-memory just once. Then load (and resize) as needed. Same for the gradient.
Even if different buttons have different colors, but the same motif, you can create a bitmap with Paint or whatever and at runtime load it and multiply the Color values by another Color.
EDIT:
if we suspendlayout of the panel before the
loop and resume layout of the panel when the loop is over
That's not what SuspendLayout and ResumeLayout are for. They suspend the layout logic, that is, the automatic positioning of the controls. Most relevant with FlowLayoutPanel and TableLayoutPanel.
As for doublebuffering, I'm not sure it applies to custom draw code (haven't tried). I guess you should implement your own.
Doublebuffering in a nutshell:
It's very simple, a couple lines of code. On the paint event, render to a bitmap instead of rendering to the Graphics object, and then draw that bitmap to the Graphics object.
In addition to the DoubleBuffered property, also try adding this to your control's constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
And if that ends up not being enough (which I'm gonna go out on a limb and say it isn't), consider having a look at my answer to this question and suspend/resume the redraw of the panel or Form. This would let your layout operations complete, then do all of the drawing once that's done.
You may want to look at the answer to my question, How do I suspend painting for a control and its children? for a better Suspend/Resume.
It sounds like what you are looking for is a "composited" display, where the entire application is drawn all at once, almost like one big bitmap. This is what happens with WPF applications, except the "chrome" surrounding the application (things like the title bar, resize handles and scrollbars).
Note that normally, unless you've messed with some of the window styles, each Windows Form control is responsible for painting itself. That is, every control gets a crack at the WM_ PAINT, WM_ NCPAINT, WM_ERASEBKGND, etc painting related messages and handles these message independently. What this means for you is that double buffering only applies to the single control you are dealing with. To get somewhat close to a clean, composited effect, you need to concern yourself not just with your custom controls that you are drawing, but also the container controls on which they are placed. For example, if you have a Form that contains a GroupBox which in turn contains a number of custom drawn buttons, each of these controls should have there DoubleBuffered property set to True. Note that this property is protected, so this means you either end up inheriting for the various controls (just to set the double buffering property) or you use reflection to set the protected property. Also, not all Windows Form controls respect the DoubleBuffered property, as internally some of them are just wrappers around the native "common" controls.
There is a way to set a composited flag if you are targeting Windows XP (and presumably later). There is the WS_ EX_ COMPOSITED window style. I have used it before to mix results. It doesn't work well with WPF/WinForm hybrid applications and also does not play well with the DataGridView control. If you go this route, be sure you do lots of testing on different machines because I've seen strange results. In the end, I abandoned used of this approach.
Maybe first draw on a control-only 'visible' (private) buffer and then render it:
In your control
BufferedGraphicsContext gfxManager;
BufferedGraphics gfxBuffer;
Graphics gfx;
A function to install graphics
private void InstallGFX(bool forceInstall)
{
if (forceInstall || gfxManager == null)
{
gfxManager = BufferedGraphicsManager.Current;
gfxBuffer = gfxManager.Allocate(this.CreateGraphics(), new Rectangle(0, 0, Width, Height));
gfx = gfxBuffer.Graphics;
}
}
In its paint method
protected override void OnPaint(PaintEventArgs e)
{
InstallGFX(false);
// .. use GFX to draw
gfxBuffer.Render(e.Graphics);
}
In its resize method
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
InstallGFX(true); // To reallocate drawing space of new size
}
The code above has been somewhat tested.
I had the same problem with a tablelayoutpanel when switching usercontrols that I wanted displayed.
I completely got rid of the flicker by creating a class that inherited the table, then enabled doublebuffering.
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
namespace myNameSpace.Forms.UserControls
{
public class TableLayoutPanelNoFlicker : TableLayoutPanel
{
public TableLayoutPanelNoFlicker()
{
this.DoubleBuffered = true;
}
}
}
I've had a lot of similar issues in the past, and the way I resolved it was to use a third-party UI suite (that is, DevExpress) rather than the standard Microsoft controls.
I started out using the Microsoft standard controls, but I found that I was constantly debugging issues which were caused by their controls. The problem is made worse by the fact that Microsoft generally does not fix any of the issues which are identified and they do very little to provide suitable workarounds.
I switched to DevExpress, and I have nothing but good things to say. The product is solid, they provide great support and documentation and yes they actually listen to their customers. Any time I had a question or an issue, I got a friendly response within 24 hours. In a couple of cases, I did find a bug and in both instances, they implemented a fix for the next service release.
I have seen bad winforms flicker on forms where the controls referred to a missing font.
This is probably not common, but it's worth looking into if you've tried everything else.

Categories

Resources