How to get the total height of a windows form including non-client area? Size does not seem to work for my window (FormBorderStyle = FixedToolWindow, if that helps).
Try the DesktopBounds property of the form.
The Size property should definitely work. Beware that the form may be rescaled due to differences in the system font or the video adapter DPI setting between the design machine and the production machine. The real size won't be available until the Load event.
Windows will lie about the Form's size if Aero is enabled and your FormBorderStyle is FixedToolWindow. I think the following code in a Form will give the correct height and width of such a Window.
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern bool DwmIsCompositionEnabled();
// When Aero is enabled, and our FormBorderStyle is FixedToolWindow,
// Windows will lie to us about our size and position.
public bool AeroIsMessingWithUs()
{
bool ret = false;
// check for other Fixed styles here if needed
if (FormBorderStyle == System.Windows.Forms.FormBorderStyle.FixedToolWindow)
{
if (Environment.OSVersion.Version.Major >= 6 && DwmIsCompositionEnabled())
{
// Aero is enabled
ret = true;
}
}
return ret;
}
public int MyWindowHeight()
{
int height = Height;
if (AeroIsMessingWithUs())
{
// there are actually 5 more pixels on the top and bottom
height += 10;
}
return height;
}
public int MyWindowWidth()
{
int width = Width;
if (AeroIsMessingWithUs())
{
// there are 5 more pixels on the left and right
width += 10;
}
return width;
}
Related
I created a code that works, but I'm not sure that it's the best way to place an Image scaled automatically to the available width space. I need to put some content over that image, so I have a LayeredLayout: in the first layer there is the Label created with the following code, on the second layer there is a BorderLayout that has the same size of the Image.
Is the following code fine or is it possible to do better?
Label background = new Label(" ", "NoMarginNoPadding") {
boolean onlyOneTime = false;
#Override
public void paint(Graphics g) {
int labelWidth = this.getWidth();
int labelHeight = labelWidth * bgImage.getHeight() / bgImage.getWidth();
this.setPreferredH(labelHeight);
if (!onlyOneTime) {
onlyOneTime = true;
this.getParent().revalidate();
}
super.paint(g);
}
};
background.getAllStyles().setBackgroundType(Style.BACKGROUND_IMAGE_SCALED_FIT);
background.getAllStyles().setBgImage(bgImage);
Shorter code:
ScaleImageLabel sl = new ScaleImageLabel(bgImage);
sl.setUIID("Container");
You shouldn't override paint to set the preferred size. You should have overriden calcPreferredSize(). For ScaleImageLabel it's already set to the natural size of the image which should be pretty big.
i have a scene in which after a particular time the slide show of
images should start.
i have created the texture array and loaded the images to array in
inspector.
now i need to slide each image after every two seconds.
this is the code i have done but it is error
public Texture[] Images;
void ChangeImage()
{
for(int i=1;i<Imagez.Length;i++)
{
print (Imagez [i].ToString());
Imag =Imagez [i];
}
}
i try to load each image from the array, but i know that its not the proper way..can any one please help
Add a scroll rect then add a panel for container and make that panel the scroll rect to control.
Then the container gets a Horizontal/VerticalLayoutGroup. Add all of your images and set the container size so they show properly.
Run the game, you should be able to scroll them appropriately (make sure you constrain the movement hor/vert so it moves correctly).
private int currentIndex = 0;
[SerializeField] private float imageSize;
[SerializeField] private RectTransform panel;
[SerializeField] private float frequency;
void Start()
{
InvokeRepeating("IncreaseCurrentIndex", frequency,frequency);
}
private void IncreaseCurrentIndex()
{
if(++this.currentIndex >= this.imageCount){ this.currentIndex = 0; }
}
void MoveToNextItem()
{
float targetX = (float)this.currentIndex * this.imageSize;
Vector2 delta = this.panel.anchoredPosition;
delta.x = Mathf.MoveTowards(delta.x, targetX, Time.deltaTime * speed);
this.panel.anchoredPosition = delta;
}
First you give the size of the image in the inspector. The panel is the container for all the images. I guess the rest is self explanatory.
The code is as simple as possible, it could benefit more flexibility but at least it should go as is.
NOTE: This is meant to be for horizontal scrolling. You would change the x for y in the update for vertical scrolling. Also, it uses Mathf.MoveTowards, you could use lerp instead of the velocity of scroll rect.
I don't know if it is even possible, but maybe someone found a way to do this...
I have a tab control to which I allow the user to add tabs with a button click.
I want to show some icons on the tab so I added an ImageList, but I can show only one icon at a time, and I need to show at least 3 icons together.
I thought about having an image of 3 icons together, but the icons are shown after some actions the use do. For example: at first I show icon_1 and if the user clicks some where I add icon_2 etc...
Can someone come up with a way to do this ?
Thank you very much in advance...
No. It's not possible. Using the standard WinForms TabControl component you only can show one image at the same time.
The solution here, is using overlay icons. You have a base icon, and you add decorators. This is how Tortoise SVN, for example,
The following code builds an overlayed image in C#:
private static object mOverlayLock = new object();
public static Image GetOverlayedImage(Image baseImage, Image overlay)
{
Image im = null;
lock (mOverlayLock)
{
try
{
im = baseImage.Clone() as Image;
Graphics g = Graphics.FromImage(im);
g.DrawImage(overlay, 0, 0, im.Width, im.Height);
g.Dispose();
}
catch
{
// log your exception here
}
}
return im;
}
NOTE: The overlayed image must have the same size than the base image. It must have transparent color, and the decorator in the overlayed image must be placed in the right place, for example bottom-right or top-right.
I found this code:
private Bitmap CombineImages(params Image[] images)
{
int width = 0;
for (int i = 0; i < images.Length; i++)
width += images[i].Width + 3;
int height = 0;
for (int i = 0; i < images.Length; i++)
{
if (images[i].Height > height)
height = images[i].Height;
}
int nIndex = 0;
Bitmap fullImage = new Bitmap(width, height);
Graphics g = Graphics.FromImage(fullImage);
g.Clear(SystemColors.AppWorkspace);
foreach (Image img in images)
{
if (nIndex == 0)
{
g.DrawImage(img, new Point(0, 0));
nIndex++;
width = img.Width;
}
else
{
g.DrawImage(img, new Point(width, 0));
width += img.Width;
}
}
return fullImage;
//img3.Save(finalImage, System.Drawing.Imaging.ImageFormat.Jpeg);
//img3.Dispose();
//imageLocation.Image = Image.FromFile(finalImage);
}
from this link http://www.codeproject.com/Articles/502249/Combineplusseveralplusimagesplustoplusformplusaplu
Using VS2010 and .NET 4.0 with C# and WinForms:
I always want a Vertical Scrollbar to show for my panel as a disabled scrollbar (when it's not needed, and a enabled one when it can be used.
So it's like a hybrid AutoScroll. I've tried using VScrollBars but I can't figure out where to place them to make this work.
Essentially I've got a user control that acts as a "Document" of controls, its size changes so when using auto-scroll it works perfectly. The scrollbar appears when the usercontrol doesn't fit and the user can move it updown.
It's like a web browser essentially. However, redrawing controls takes a long time (it's forms with many fields and buttons etc within groups in a grid within a panel :P
So anyhow, when autoscroll enables the vertical scrollbar, it takes a while to redraw the window. I'd like to ALWAYS show the vertical scrollbar as indicated above (with the enable/disable functionality).
If anyone has some help, i've read many posts on the subject of autoscroll, but noone has asked what I'm asking and I can't come up with a solution.
C# Version of competent_Tech's answer
using System.Runtime.InteropServices;
public class MyUserControl : UserControl
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool ShowScrollBar(IntPtr hWnd, int wBar, bool bShow);
private enum ScrollBarDirection
{
SB_HORZ = 0,
SB_VERT = 1,
SB_CTL = 2,
SB_BOTH = 3
}
public MyUserControl()
{
InitializeComponent();
ShowScrollBar(this.Handle, (int) ScrollBarDirection.SB_VERT, true);
}
}
You can use the auto-scroll functionality of the panel, you just need to send it a windows message to show the vertical scrollbar:
<DllImport("user32.dll")> _
Public Shared Function ShowScrollBar(ByVal hWnd As System.IntPtr, ByVal wBar As Integer, ByVal bShow As Boolean) As Boolean
End Function
Private Const SB_VERT As Integer = 1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
ShowScrollBar(Panel1.Handle, SB_VERT, True)
End Sub
The scrollbar will be displayed and appear as though it can be scrolled, but it won't do anything until it is actually ready to scroll. If you disable it, it won't be automatically re-enabled, so this is probably the best approach.
Also, to improve the performance while resizing, you can call SuspendLayout on the panel before updating and ResumeLayout when done.
What worked for me was overriding the CreateParams call and enabling the WS_VSCROLL style.
public class VerticalFlowPanel : FlowLayoutPanel
{
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.Style |= 0x00200000; // WS_VSCROLL
return cp;
}
}
}
The AutoScroll logic will now adjust the scrolling bounds without ever hiding the scrollbar.
Here is what solved this for me. My case is that I have a panel sandwiched between another three panels with no degree of liberty in any direction. I needed this panel to be so big that the whole structure would go out of my 1920x1080 screen.
The solution is actually very simple.
For the panel that needs scroll bars set the AutoScroll property to true. Then, add on it another control in the far right far down position (right-bottom position). The control I choose is a label which I made invisible.... And that is all.
Now my panel occupies its restricted area, but I can scroll to the size that I needed and use it for the size I need.
If you only need horizontal scroll bars add the invisible control outside left, for vertical only far down bottom.
The actual size of the panel is the one you restrict it to when display it, but the virtual size is dictated by the invisible control.
This code will draw a disabled vertical scrollbar whenever the built in scrollbar of the Panel is invisible. The codes assumes that
AutoScroll = true;
AutoSize = false;
The following code is speed-optimized. It does as few as possible in OnPaint().
Derive a class from Panel.
Add these member variables:
// NOTE: static variables are not thread safe.
// But as we have only one GUI thread this does not matter.
static IntPtr mh_ScrollTheme = IntPtr.Zero;
static int ms32_ScrollWidth = SystemInformation.VerticalScrollBarWidth;
Win32.RECT mk_ScrollTop;
Win32.RECT mk_ScrollBot; // coordinates of top scrollbar button
Win32.RECT mk_ScrollShaft; // coordinates of bottom scrollbar button
Then override OnSizeChanged:
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
Win32.RECT k_ScrollBar = new Win32.RECT(ClientRectangle);
k_ScrollBar.Left = k_ScrollBar.Right - ms32_ScrollWidth;
mk_ScrollTop = new Win32.RECT(k_ScrollBar);
mk_ScrollBot = new Win32.RECT(k_ScrollBar);
mk_ScrollShaft = new Win32.RECT(k_ScrollBar);
int s32_Upper = k_ScrollBar.Top + ms32_ScrollWidth;
int s32_Lower = k_ScrollBar.Bottom - ms32_ScrollWidth;
mk_ScrollTop .Bottom = s32_Upper;
mk_ScrollBot .Top = s32_Lower;
mk_ScrollShaft.Top = s32_Upper;
mk_ScrollShaft.Bottom = s32_Lower;
}
And paint the scrollbar when required:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (VScroll)
return; // The 'real' scrollbar is visible
if (mh_ScrollTheme == IntPtr.Zero)
mh_ScrollTheme = Win32.OpenThemeData(Handle, "SCROLLBAR");
if (mh_ScrollTheme == IntPtr.Zero)
return; // The user has disabled themes
// Draw the disabled vertical scrollbar.
IntPtr h_DC = e.Graphics.GetHdc();
// Draw shaft
const int SBP_UPPERTRACKVERT = 7;
const int SCRBS_DISABLED = 4;
Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_UPPERTRACKVERT, SCRBS_DISABLED, ref mk_ScrollShaft, IntPtr.Zero);
// Draw top button
const int SBP_ARROWBTN = 1;
const int ABS_UPDISABLED = 4;
Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_ARROWBTN, ABS_UPDISABLED, ref mk_ScrollTop, IntPtr.Zero);
// Draw lower button
const int ABS_DOWNDISABLED = 8;
Win32.DrawThemeBackground(mh_ScrollTheme, h_DC, SBP_ARROWBTN, ABS_DOWNDISABLED, ref mk_ScrollBot, IntPtr.Zero);
e.Graphics.ReleaseHdc(h_DC);
}
For some years, the answer of BradJ and fiat worked for me. Now I needed to show the disabled scrollbar look. But I failed to find the correct way… So here is my workaround.
The code bellow just draw the disabled scrollbar at the position of the real scrollbar.
THE CODE
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
public class VerticalFlowPanel : FlowLayoutPanel
{
public VerticalFlowPanel()
{
AutoScroll = true;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var width = Width;
var height = Height;
var vsWidth = SystemInformation.VerticalScrollBarWidth;
var vsHeight = SystemInformation.VerticalScrollBarArrowHeight;
var left = width - vsWidth;
var sbUpper = new Rectangle(left, 0, vsWidth, height / 2);
var sbLower = new Rectangle(left, sbUpper.Height, vsWidth, height - sbUpper.Height);
var arUp = new Rectangle(left, 0, vsWidth, vsHeight);
var arDown = new Rectangle(left, height - vsHeight, vsWidth, vsHeight);
ScrollBarRenderer.DrawUpperVerticalTrack(e.Graphics, sbUpper, ScrollBarState.Disabled);
ScrollBarRenderer.DrawLowerVerticalTrack(e.Graphics, sbLower, ScrollBarState.Disabled);
ScrollBarRenderer.DrawArrowButton(e.Graphics, arUp, ScrollBarArrowButtonState.UpDisabled);
ScrollBarRenderer.DrawArrowButton(e.Graphics, arDown, ScrollBarArrowButtonState.DownDisabled);
}
// Necessary to avoid visual artifacts
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
var width = Width;
var height = Height;
var vsWidth = SystemInformation.VerticalScrollBarWidth;
var scrollBounds = new Rectangle(width - vsWidth, 0, vsWidth, height);
Invalidate(scrollBounds);
}
}
NOTE
This is not the best solution. But it was easier than migrate my hole solution to WPF…
For a System.Windows.Forms.TextBox with Multiline=True, I'd like to only show the scrollbars when the text doesn't fit.
This is a readonly textbox used only for display. It's a TextBox so that users can copy the text out. Is there anything built-in to support auto show of scrollbars? If not, should I be using a different control? Or do I need to hook TextChanged and manually check for overflow (if so, how to tell if the text fits?)
Not having any luck with various combinations of WordWrap and Scrollbars settings. I'd like to have no scrollbars initially and have each appear dynamically only if the text doesn't fit in the given direction.
#nobugz, thanks, that works when WordWrap is disabled. I'd prefer not to disable wordwrap, but it's the lesser of two evils.
#André Neves, good point, and I would go that way if it was user-editable. I agree that consistency is the cardinal rule for UI intuitiveness.
I came across this question when I wanted to solve the same problem.
The easiest way to do it is to change to System.Windows.Forms.RichTextBox. The ScrollBars property in this case can be left to the default value of RichTextBoxScrollBars.Both, which indicates "Display both a horizontal and a vertical scroll bar when needed." It would be nice if this functionality were provided on TextBox.
Add a new class to your project and paste the code shown below. Compile. Drop the new control from the top of the toolbox onto your form. It's not quite perfect but ought to work for you.
using System;
using System.Drawing;
using System.Windows.Forms;
public class MyTextBox : TextBox {
private bool mScrollbars;
public MyTextBox() {
this.Multiline = true;
this.ReadOnly = true;
}
private void checkForScrollbars() {
bool scroll = false;
int cnt = this.Lines.Length;
if (cnt > 1) {
int pos0 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(0)).Y;
if (pos0 >= 32768) pos0 -= 65536;
int pos1 = this.GetPositionFromCharIndex(this.GetFirstCharIndexFromLine(1)).Y;
if (pos1 >= 32768) pos1 -= 65536;
int h = pos1 - pos0;
scroll = cnt * h > (this.ClientSize.Height - 6); // 6 = padding
}
if (scroll != mScrollbars) {
mScrollbars = scroll;
this.ScrollBars = scroll ? ScrollBars.Vertical : ScrollBars.None;
}
}
protected override void OnTextChanged(EventArgs e) {
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnClientSizeChanged(EventArgs e) {
checkForScrollbars();
base.OnClientSizeChanged(e);
}
}
I also made some experiments, and found that the vertical bar will always show if you enable it, and the horizontal bar always shows as long as it's enabled and WordWrap == false.
I think you're not going to get exactly what you want here. However, I believe that users would like better Windows' default behavior than the one you're trying to force. If I were using your app, I probably would be bothered if my textbox real-estate suddenly shrinked just because it needs to accomodate an unexpected scrollbar because I gave it too much text!
Perhaps it would be a good idea just to let your application follow Windows' look and feel.
There's an extremely subtle bug in nobugz's solution that results in a heap corruption, but only if you're using AppendText() to update the TextBox.
Setting the ScrollBars property from OnTextChanged will cause the Win32 window (handle) to be destroyed and recreated. But OnTextChanged is called from the bowels of the Win32 edit control (EditML_InsertText), which immediately thereafter expects the internal state of that Win32 edit control to be unchanged. Unfortunately, since the window is recreated, that internal state has been freed by the OS, resulting in an access violation.
So the moral of the story is: don't use AppendText() if you're going to use nobugz's solution.
I had some success with the code below.
public partial class MyTextBox : TextBox
{
private bool mShowScrollBar = false;
public MyTextBox()
{
InitializeComponent();
checkForScrollbars();
}
private void checkForScrollbars()
{
bool showScrollBar = false;
int padding = (this.BorderStyle == BorderStyle.Fixed3D) ? 14 : 10;
using (Graphics g = this.CreateGraphics())
{
// Calcualte the size of the text area.
SizeF textArea = g.MeasureString(this.Text,
this.Font,
this.Bounds.Width - padding);
if (this.Text.EndsWith(Environment.NewLine))
{
// Include the height of a trailing new line in the height calculation
textArea.Height += g.MeasureString("A", this.Font).Height;
}
// Show the vertical ScrollBar if the text area
// is taller than the control.
showScrollBar = (Math.Ceiling(textArea.Height) >= (this.Bounds.Height - padding));
if (showScrollBar != mShowScrollBar)
{
mShowScrollBar = showScrollBar;
this.ScrollBars = showScrollBar ? ScrollBars.Vertical : ScrollBars.None;
}
}
}
protected override void OnTextChanged(EventArgs e)
{
checkForScrollbars();
base.OnTextChanged(e);
}
protected override void OnResize(EventArgs e)
{
checkForScrollbars();
base.OnResize(e);
}
}
What Aidan describes is almost exactly the UI scenario I am facing. As the text box is read only, I don't need it to respond to TextChanged. And I'd prefer the auto-scroll recalculation to be delayed so it's not firing dozens of times per second while a window is being resized.
For most UIs, text boxes with both vertical and horizontal scroll bars are, well, evil, so I'm only interested in vertical scroll bars here.
I also found that MeasureString produced a height that was actually bigger than what was required. Using the text box's PreferredHeight with no border as the line height gives a better result.
The following seems to work pretty well, with or without a border, and it works with WordWrap on.
Simply call AutoScrollVertically() when you need it, and optionally specify recalculateOnResize.
public class TextBoxAutoScroll : TextBox
{
public void AutoScrollVertically(bool recalculateOnResize = false)
{
SuspendLayout();
if (recalculateOnResize)
{
Resize -= OnResize;
Resize += OnResize;
}
float linesHeight = 0;
var borderStyle = BorderStyle;
BorderStyle = BorderStyle.None;
int textHeight = PreferredHeight;
try
{
using (var graphics = CreateGraphics())
{
foreach (var text in Lines)
{
var textArea = graphics.MeasureString(text, Font);
if (textArea.Width < Width)
linesHeight += textHeight;
else
{
var numLines = (float)Math.Ceiling(textArea.Width / Width);
linesHeight += textHeight * numLines;
}
}
}
if (linesHeight > Height)
ScrollBars = ScrollBars.Vertical;
else
ScrollBars = ScrollBars.None;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
finally
{
BorderStyle = borderStyle;
ResumeLayout();
}
}
private void OnResize(object sender, EventArgs e)
{
m_timerResize.Stop();
m_timerResize.Tick -= OnDelayedResize;
m_timerResize.Tick += OnDelayedResize;
m_timerResize.Interval = 475;
m_timerResize.Start();
}
Timer m_timerResize = new Timer();
private void OnDelayedResize(object sender, EventArgs e)
{
m_timerResize.Stop();
Resize -= OnResize;
AutoScrollVertically();
Resize += OnResize;
}
}