With one line of code, I can cause VS2012 to crash consistently. (By "crash" I mean when I hit Build, Visual Studio hangs indefinitely and I have to kill it from Task Manager.) Here's the code (in a custom user control):
public class TransparentPanel : System.Windows.Forms.Panel
{
protected override void OnPaintBackground(PaintEventArgs pe)
{
this.Invalidate(); //this is the offending line
}
}
To reproduce the problem, create a control based on the above code and add it to a Windows Form; then try to Build Solution. "Build started..." will be displayed in the Status Bar and then VS will immediately and permanently freeze up. I tried troubleshooting, using devenv /log, but the Activity Log displayed no errors or warnings. So my question is, why is this code fatal to the C# compiler? (It causes problems in the IDE too. For example, after adding this transparent control, the Properties pane becomes frozen whenever the Form Designer is open.)
Side question: should this bug be reported to Microsoft? If so, how? (I tried to submit the bug on the MS Connect site, but apparently they are only accepting bugs for VS2013.)
[If you want to know the background of why I was trying to use that line, read on.]
I created a custom (Windows Forms) control for an application. (I was trying to create a control with a partially transparent image, whose location could be changed through mouse interactions.)
I used the following code, which gave me the image (on a panel) with the transparency I was looking for:
public class TransparentPanel : System.Windows.Forms.Panel
{
public Image Image { get; set; }
protected override void OnPaint(PaintEventArgs pe)
{
Rectangle rect = new Rectangle(this.Location, this.Size);
if (Image != null)
pe.Graphics.DrawImage(Image, this.DisplayRectangle);
}
protected override void OnPaintBackground(PaintEventArgs pe)
{
//this.Invalidate();
Brush brush = new SolidBrush(Color.FromArgb(0, 0, 0, 0));
pe.Graphics.FillRectangle(brush, pe.ClipRectangle);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20; // WS_EX_TRANSPARENT
return cp;
}
}
}
However, the transparency doesn't "stick". When the control is relocated, for example with this event handler:
private void transparentPanel1_MouseClick(object sender, MouseEventArgs e)
{
int y = this.transparentPanel1.Location.Y ;
int x = this.transparentPanel1.Location.X ;
this.transparentPanel1.Location = new System.Drawing.Point(x-5, y-5);
}
...the transparent portion of the control retains the same background it had when it was first painted. (This can only be seen if another control is placed behind it. Sorry, hard to describe.) So I was attempting to use the Invalidate() to repaint the control, and therefore, repaint the transparent portion to match whatever was behind the control after the relocation. This is when the VS bug appeared. (Actually I didn't notice the bug right away, so it was rather excruciating to isolate the offending line of code.)
public class TransparentPanel : System.Windows.Forms.Panel
{
protected override void OnPaintBackground(PaintEventArgs pe)
{
this.Invalidate(); //this is the offending line
}
}
That is akin to an infinite loop.
When you panel repaints, your handler is called... and you tell it to invalidate itself... which causes a repaint... which calls your handler... etc.
The designer loses its mind. You don't need or want to invalidate the control from within OnPaintBackground, unless it is conditional (still rare).
Related
Environment: VS 2012 on Win7 Pro box.
I have a control I derived from TextBox. The control's text is initialized to "00". I want to make sure that the cursor is positioned at the end of the text string, if it exists, whenever the control becomes active.
I have three of these controls contained in a user control. I can only get the cursor positioning to work the way I want it if all of the following are true:
I have overridden OnEnter() in my textbox-derived control.
I have overridden OnGotFocus() in my textbox-derived control.
I am running my test form in the debugger.
I have a breakpoint set at the start of OnEnter().
Here are my overrides:
protected override void OnEnter(EventArgs e)
{
base.OnEnter(e);
if (Text.Length > 0)
{
SelectionStart = Text.Length;
}
}
protected override void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
if (Text.Length > 0)
{
SelectionStart = Text.Length;
}
}
Thank you very much.
The code you wrote does work when you switch to the textbox using the tab key (I just tested it to confirm).
It doesn't work when you click the textbox with the mouse. The reason for that is probably due to the fact that dot net sets the caret to the mouse cursor location, and it does that after the GotFocus event gets fired (and the GotFocus event is fired after the Enter event is fired, according to this link: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.gotfocus(v=vs.110).aspx).
I don't have an elegant solution to this, but until somebody comes up with something better, you can add a small delay (which is what happens when you break with the debugger) which seems to work fine for me:
protected override async void OnGotFocus(EventArgs e)
{
base.OnGotFocus(e);
await Task.Delay(10);
if (Text.Length > 0)
{
SelectionStart = Text.Length;
}
}
I've written a user control for that supports transparent background (among other things).
However, I've found a problem that when the background is transparent, and you change the text of the user control, the previous text is still displayed on the screen, under the new text, making it impossible to read.
I've been googling for half a day now, finding all sort of suggestions that didn't work in my case, most of them involving painting the parent control onto a bitmap and draw that bitmap on my control's surface.
However, in my case the parent control is also transparent so I've tried to go up to the form's level like suggested here but i was getting an InvalidArgumentException,
I've tried invalidating the parent control like suggested here but no luck either.
my code is basically this (truncated to the bare minimum):
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
if(this.BackColor != Color.Transparent)
{
base.OnPaintBackground(e);
}
}
I keep learning again and again that simply explaining the problem to someone else often helps you solve it.
So, After combining a few answers from all my searches,
mainly this one about invalidating a specified rectangle of the parent,
and this one about getting the location of the control on the form,
I came up with this:
// Make the Text property browsable again, call Refresh when changed.
[Browsable(true),
DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override string Text
{
get { return _Text; }
set
{
if(_Text != value)
{
this.Refresh();
_Text = value;
}
}
}
// Override Refresh to invalidate the relevant part of the parent form
public override void Refresh()
{
Form form = this.FindForm();
// only for transparent controls that has text and no background image
if (this.BackColor == Color.Transparent &&
!string.IsNullOrEmpty(this.Text) &&
(this.Gradient.BackColor2==Color.Transparent || !this.Gradient.IsGradient) &&
this.BackgroundImage == null &&
form != null)
{
Point locationOnForm = form.PointToClient(
this.Parent.PointToScreen(this.Location)
);
// Invalidate the rectangle of the form that's behind the current control
form.Invalidate(new Rectangle(locationOnForm, this.Size));
}
base.Invalidate();
}
I will still have to deal with parent controls between the current control and the form, but for now this is good enough for me.
I think we are close on this one.
Firstly, you cannot suppress OnPaintBackground like that and expect the rest of winforms to run as expected, this means nothing gets painted to that region at all, so we have left GDI+ or windows or presumably some other construct to "fill in the blanks". The controls on your form that are opaque may want to call your UserControl to get it's background, so we have to paint something...
So we always call base.OnPaintBackground, but if the background is Transparent, then we need to invalidate the entire surface of the control to be repainted.
public Glass()
{
InitializeComponent();
this.SetStyle(ControlStyles.Opaque, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x20;
return cp;
}
}
private bool rePaintState = false;
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
if (this.BackColor == Color.Transparent)
{
// we want to invalidate and force it to re-paint but just once
if (rePaintState)
{
rePaintState = false;
}
else
{
rePaintState = true;
this.Invalidate();
}
}
}
This is still not complete, but saved most of the flickering I was experiencing when controls above and below the UserControl had textboxes with values refreshing on a timer.
I know its not a huge help, but WPF is really good for these types of UX issues ^-^
I have the following behavior which is unfinished. Please note that TransitionElement is basically a ContentControl.
I would like to create two storyboards that use BlurBitmapEffect to blur and unblur the control.
If the ContentControl Enabled property is set to false, I would like to add the storyboards and begin the blur.
If Enabled is set to true, I would like to run a storyboard that unblurs the control and once finished removes both storyboards, effectively removing any bitmap effects.
class ContentControlBehavior : Behavior<TransitionElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.IsEnabledChanged += AssociatedObject_IsEnabledChanged;
}
protected override void OnDetaching()
{
AssociatedObject.IsEnabledChanged -= AssociatedObject_IsEnabledChanged;
base.OnDetaching();
}
void AssociatedObject_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
//Blur
}
else
{
//UnBlur and remove storyboards and any bitmap effects.
}
}
}
I am doing this when I lock and unlock my application.
I found doing it through XAML with styles had a huge impact on graphical performance. Then I found some notes below. So I figure why not apply these storyboards and then remove them altogther.
Be careful using WPF bitmap effects. At the time I'm writing this, WPF
bitmap effects are rendered in software mode. Any object that applies
an effect will also be rendered in software. Bitmap effects should not
be applied to large visuals. Also, animating properties of an effect
can degrade performance. At this time, I suggest that you use bitmap
effects sparingly and use them on relatively small visual UI objects
like buttons, text boxes, etc. Feel free to animate effects, but
again, I recommend relatively small, subtle animations.
I have just noticed that BitmapEffect is depreciated and Effect is the one to use.
here is a solution if you do not want to remove the storyboard at the end of unblur
assuming TransitionElement is a kind of FrameworkElement below is a sample.
class ContentControlBehavior : Behavior<TransitionElement>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.IsEnabledChanged += AssociatedObject_IsEnabledChanged;
// add effect to element
BlurEffect effect = new BlurEffect() { Radius = 0 };
AssociatedObject.Effect = effect;
}
protected override void OnDetaching()
{
AssociatedObject.IsEnabledChanged -= AssociatedObject_IsEnabledChanged;
base.OnDetaching();
//remove the effect
AssociatedObject.Effect = null;
}
void AssociatedObject_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue == false)
{
//Blur
BlurEffect effect = AssociatedObject.Effect as BlurEffect;
effect.BeginAnimation(BlurEffect.RadiusProperty, new DoubleAnimation(10, TimeSpan.FromSeconds(0.5)));
}
else
{
//UnBlur
BlurEffect effect = AssociatedObject.Effect as BlurEffect;
effect.BeginAnimation(BlurEffect.RadiusProperty, new DoubleAnimation(0, TimeSpan.FromSeconds(0.25)));
}
}
}
in above example invoking BeginAnimation effectively removes any previous animation from the targeted property, however last one remain, but that is on the effect and will be removed as the behavior is Detached.
I have a user control with a scrollbar (scrollbar appears as a contained user control, which inherits from Panel, is too large). When using the mouse to scroll all is well, but trying to scroll with the mousewheel dont work.
My solution here is to set focus to my child-control in an eventhandler for Scroll. This works. Now the question; Will this result in a lot of unecessary calls to childControl.Focus()? Is there a more neat way of doing this?
Edit: I think I was a bit unclear with my question so Rephrasing the question:
is
private void ChildControl_OnScroll(object sender, ScrollEventArgs scrollEventArgs)
{
this.childControl.Focus();
}
a bad way of setting the focus? I.e. will the focus be set mutliple times each time I scroll? or rather, will this cause (tiny) performance issues.
Here's another approach that gives focus when the scrollbar area of panel1 inside SomeUserControl is clicked. It uses NativeWindow so you don't have to change the panel in your UserControl. This way Focus() will only be called once, when the mouse goes down in the scrollbar area:
public partial class SomeUserControl : UserControl
{
private TrapMouseDownOnScrollArea trapScroll = null;
public SomeUserControl()
{
InitializeComponent();
this.VisibleChanged += new EventHandler(SomeUserControl_VisibleChanged);
}
void SomeUserControl_VisibleChanged(object sender, EventArgs e)
{
if (this.Visible && trapScroll == null)
{
trapScroll = new TrapMouseDownOnScrollArea(this.panel1);
}
}
private class TrapMouseDownOnScrollArea : NativeWindow
{
private Control control = null;
private const int WM_NCLBUTTONDOWN = 0xA1;
public TrapMouseDownOnScrollArea(Control ctl)
{
if (ctl != null && ctl.IsHandleCreated)
{
this.control = ctl;
this.AssignHandle(ctl.Handle);
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_NCLBUTTONDOWN:
if (this.control != null)
{
Rectangle screenBounds = control.RectangleToScreen(new Rectangle(0, 0, control.Width, control.Height));
if (screenBounds.Contains(Cursor.Position))
{
control.Focus();
}
}
break;
}
base.WndProc(ref m);
}
}
}
This might be overkill for your scenario, but it demonstrates one way to trap lower level messages. As said before, you could also derive from Panel to achieve the same affect. You could also trap messages at the application level with IMessageFilter.
The MouseWheel event is an event that "bubbles". Windows sends it to the control that has the focus, regardless of where the mouse cursor is located. The most typical problem is that you have a control that cannot receive the focus. A Panel for example.
This changes when you put a control on the panel. Now that control can get the focus and gets the MouseWheel message. It won't have any use for it so the message passes to its parent. Which does have a use for it, the panel scrolls as expected.
You can get a focusable panel control from this answer. A generic "make it work like a browser or Office program" solution from this question
If childControl has a MouseEnter() event then use that instead:
private void childControl_MouseEnter(object sender, EventArgs e)
{
childControl.Focus();
}
Then the mouse wheel events should be direct to childControl.
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How can I remove the border padding on container controls in WinForms?
I've developed a Winforms application in Visual studio 2008. On the main form I have a Tab control. Now I am trying to use a background image to the tab page. The problem that I am running into is that the tab control seems to have a thick border around it. Also the tab control does not cover the entire form leaving a line of space over the top between the form and tab page. (I have the tab pages alignment set at bottom). So a border around the tab control and line of space at the top making my page look ugly. I tried to give the same image as the background to form, but the tab control padding playing the spoilsport.
Any ideas to make my design better would be appreciated.
I agree with the majority of comments made here. The standard TabControl is drawn very poorly on Microsoft's Part...even in Windows Vista / 7 it doesn't look great! You would be better to write your own custom implementation by inheriting the TabControl and then drawing the additional stuff you want.
You might consider using this as a template for your new control. You just need to add some cool design / drawing work to the OnPaint and OnPaintBackground methods.
namespace CustomControls
{
#region USING
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
#endregion
public class CustomTabControl : TabControl
{
#region VARIABLES
private int hotTrackTab = -1;
#endregion
#region INSTANCE CONSTRUCTORS
public CustomTabControl() : base()
{
this.InitializeComponent();
}
#endregion
#region INSTANCE METHODS
private void InitializeComponent()
{
this.SetStyle(ControlStyles.UserPaint, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.ResizeRedraw, true);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.DrawMode = TabDrawMode.OwnerDrawFixed;
}
private int GetTabUnderCursor()
{
Point cursor = this.PointToClient(Cursor.Position);
for (int index = 0; index < this.TabPages.Count; index++)
{
if (this.GetTabRect(index).Contains(cursor))
{
return index;
}
}
return -1;
}
private void UpdateHotTrack()
{
int hot = GetTabUnderCursor();
if (hot != this.hotTrackTab)
{
if (this.hotTrackTab != -1)
{
this.Invalidate(this.GetTabRect(this.hotTrackTab));
}
this.hotTrackTab = hot;
if (this.hotTrackTab != -1)
{
this.Invalidate(this.GetTabRect(this.hotTrackTab));
}
this.Update();
}
}
#endregion
#region OVERRIDE METHODS
protected override void OnMouseEnter(EventArgs e)
{
base.OnMouseEnter(e);
this.UpdateHotTrack();
}
protected override void OnMouseLeave(EventArgs e)
{
base.OnMouseLeave(e);
this.UpdateHotTrack();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
this.UpdateHotTrack();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
switch (this.Alignment)
{
case TabAlignment.Bottom:
case TabAlignment.Left:
case TabAlignment.Right:
case TabAlignment.Top:
default:
throw new NotImplementedException();
}
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
base.OnPaintBackground(pevent);
}
#endregion
}
}
Bear in mind that the code above draw an absolutely blank TabControl that only displays the DisplayRectangle. Everything else including the tabs you will need to do yourself!
Additionally, to draw backgrounds for individual TabPages, you may need to override and custom implement TabPage as well, however you might be able to achieve the result you are looking for with just a custom tab control.
Check this out
http://www.codeproject.com/Articles/42046/Customized-TabControl-by-Repainting-Microsoft-s-Pa
IMHO This is better...
http://www.codeproject.com/Articles/38014/KRBTabControl
IMHO this is better still...
http://www.codeproject.com/Articles/91387/Painting-Your-Own-Tabs-Second-Edition
also look on VB Forums...I know I've seem some awesome custom tab controls on there!