Determine if an open WPF window is visible on any monitor - wpf

Is there a way to determine if an open WPF window is currently visible in any of the desktop's connected monitors? By visible I mean that the window's bounds rectangle intersects with the desktop rectangle of any of the monitors.
I need this functionality to determine if a window needs to be repositioned because the monitor configuration (working areas bounds, monitor count) changed between restarts of my application (which saves window positions).
I have come up with the code below and it seems to work, but it has several problems:
I need to reference windows forms.
I need the desktop's DPI settings and transform the windows forms actual pixels to WPF virtual pixels.
I need an acutal Visual instance that already has been rendered to perform the transformation.
Do you know of a solution that gets rid of some or all of the 3 issues above?
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
internal static class Desktop
{
private static Size dpiFactor = new Size(1.0, 1.0);
private static bool isInitialized;
public static IEnumerable<Rect> WorkingAreas
{
get
{
return
Screen.AllScreens.Select(
screen =>
new Rect(
screen.WorkingArea.Left * dpiFactor.Width,
screen.WorkingArea.Top * dpiFactor.Height,
screen.WorkingArea.Width * dpiFactor.Width,
screen.WorkingArea.Height * dpiFactor.Height));
}
}
public static void TryInitialize(Visual visual)
{
if (isInitialized)
{
return;
}
var ps = PresentationSource.FromVisual(visual);
if (ps == null)
{
return;
}
var ct = ps.CompositionTarget;
if (ct == null)
{
return;
}
var m = ct.TransformToDevice;
dpiFactor = new Size(m.M11, m.M22);
isInitialized = true;
}
}
Usage of the (initialized) Desktop class:
private bool IsLocationValid(Rect windowRectangle)
{
foreach (var workingArea in Desktop.WorkingAreas)
{
var intersection = Rect.Intersect(windowRectangle, workingArea);
var minVisible = new Size(10.0, 10.0);
if (intersection.Width >= minVisible.Width &&
intersection.Height >= minVisible.Height)
{
return true;
}
}
return false;
}
Update
Using the virtual screen (SystemParameters.VirtualScreen*) does not work because when using multiple monitors the "desktop" is not a simple rectangle. It might be a polygon. There will be blind spots in the virtual screen because
the connected screens can have different resolutions
you can configure the position of each screen.

The code we use to do something similar uses information from SystemParameters, in particular SystemParameter.VirtualScreenLeft, Top, Width and Height.
If we have a saved Location and Size then we determine if the window is out of bounds like this:
bool outOfBounds =
(location.X <= SystemParameters.VirtualScreenLeft - size.Width) ||
(location.Y <= SystemParameters.VirtualScreenTop - size.Height) ||
(SystemParameters.VirtualScreenLeft +
SystemParameters.VirtualScreenWidth <= location.X) ||
(SystemParameters.VirtualScreenTop +
SystemParameters.VirtualScreenHeight <= location.Y);

I use this code snipped in my WPF project on startup; it's written in vb.net:
If My.Settings.RememberWindowPositionAndSize Then
If My.Settings.winMainWinState > 2 Then My.Settings.winMainWinState = WindowState.Normal
If My.Settings.winMainWinState = WindowState.Minimized Then My.Settings.winMainWinState = WindowState.Normal
Me.WindowState = My.Settings.winMainWinState
If My.Settings.winMainWinState = WindowState.Normal Then
Dim winBounds As New System.Drawing.Rectangle(CInt(My.Settings.winMainPosX), CInt(My.Settings.winMainPosY),
CInt(My.Settings.winMainSizeB), CInt(My.Settings.winMainSizeH))
For Each scr As System.Windows.Forms.Screen In System.Windows.Forms.Screen.AllScreens
If winBounds.IntersectsWith(scr.Bounds) Then
Me.Width = My.Settings.winMainSizeB
Me.Height = My.Settings.winMainSizeH
Me.Top = My.Settings.winMainPosY
Me.Left = My.Settings.winMainPosX
Exit For
End If
Next
End If
End If
and here is same (converted) code in C#
if (My.Settings.RememberWindowPositionAndSize) {
if (My.Settings.winMainWinState > 2)
My.Settings.winMainWinState = WindowState.Normal;
if (My.Settings.winMainWinState == WindowState.Minimized)
My.Settings.winMainWinState = WindowState.Normal;
this.WindowState = My.Settings.winMainWinState;
if (My.Settings.winMainWinState == WindowState.Normal) {
System.Drawing.Rectangle winBounds = new System.Drawing.Rectangle(Convert.ToInt32(My.Settings.winMainPosX), Convert.ToInt32(My.Settings.winMainPosY), Convert.ToInt32(My.Settings.winMainSizeB), Convert.ToInt32(My.Settings.winMainSizeH));
foreach (System.Windows.Forms.Screen scr in System.Windows.Forms.Screen.AllScreens) {
if (winBounds.IntersectsWith(scr.Bounds)) {
this.Width = My.Settings.winMainSizeB;
this.Height = My.Settings.winMainSizeH;
this.Top = My.Settings.winMainPosY;
this.Left = My.Settings.winMainPosX;
break;
}
}
}
}

This code checks if a window top left corner is inside the Virtual Screen box (the rectangle that includes all the available screens). It also takes care of multimonitor set ups where coordinates can be negative, for example primary monitor on the right or on the bottom.
bool valid_position =
SystemParameters.VirtualScreenLeft <= saved_location.X &&
(SystemParameters.VirtualScreenLeft + SystemParameters.VirtualScreenWidth) >= saved_location.X &&
SystemParameters.VirtualScreenTop <= saved_location.Y &&
(SystemParameters.VirtualScreenTop + SystemParameters.VirtualScreenHeight) >= saved_location.Y;

Related

How to debug layout performance problems in WPF?

I'm writing a custom control for WPF (a drawn one) and I'm having massive performance issues. The fact is that I'm drawing a lots of text and this might be a part of the problem. I timed the OnRender method however, and I'm faced with very weird results - the whole method (especially after moving to GlyphRun implemenation) takes around 2-3ms to complete. Everything looks like following (take a look at the Output window for debug timing results) (requires Flash to play):
https://www.screencast.com/t/5p6mC6rxFv0
The OnRender method doesn't have anything special in particular, it just renders some rectangles and text:
protected override void OnRender(DrawingContext drawingContext)
{
var stopwatch = Stopwatch.StartNew();
ValidateMetrics();
base.OnRender(drawingContext);
var pixelsPerDip = VisualTreeHelper.GetDpi(this).PixelsPerDip;
// Draw header
DrawHeader(drawingContext, pixelsPerDip);
// Draw margin
DrawMargin(drawingContext, pixelsPerDip);
// Draw data
DrawData(drawingContext, pixelsPerDip);
// Draw footer
DrawFooter(drawingContext);
stopwatch.Stop();
System.Diagnostics.Debug.WriteLine($"Drawing took {stopwatch.ElapsedMilliseconds}ms");
}
I ran Visual Studio's performance analysis and got the following results:
Clearly "Layout" of the editor control takes a lot of time, but still rendering is very quick.
How to debug this performance issue further? Why performance is so low despite OnRender taking milliseconds to run?
Edit - as response to comments
I didn't find a good way to time drawing, though I found, what was the cause of the problem and it turned out to be text drawing. I ended up with using very low-level text drawing mechanism, using GlyphRuns and it turned out to be fast enough to display full HD worth of text at least 30 frames per second, what was enough for me.
I'll post some relevant pieces of code below. Please note though: this is not ready-to-use solution, but should point anyone interested in the right direction.
private class GlyphRunInfo
{
public GlyphRunInfo()
{
CurrentPosition = 0;
}
public void FillMissingAdvanceWidths()
{
while (AdvanceWidths.Count < GlyphIndexes.Count)
AdvanceWidths.Add(0);
}
public List<ushort> GlyphIndexes { get; } = new List<ushort>();
public List<double> AdvanceWidths { get; } = new List<double>();
public double CurrentPosition { get; set; }
public double? StartPosition { get; set; }
}
// (...)
private void BuildTypeface()
{
typeface = new Typeface(FontFamily);
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
{
typeface = null;
glyphTypeface = null;
}
}
private void AddGlyph(char c, double position, GlyphRunInfo info)
{
if (glyphTypeface.CharacterToGlyphMap.TryGetValue(c, out ushort glyphIndex))
{
info.GlyphIndexes.Add(glyphIndex);
if (info.GlyphIndexes.Count > 1)
info.AdvanceWidths.Add(position - info.CurrentPosition);
info.CurrentPosition = position;
if (info.StartPosition == null)
info.StartPosition = info.CurrentPosition;
}
}
private void DrawGlyphRun(DrawingContext drawingContext, GlyphRunInfo regularRun, Brush brush, double y, double pixelsPerDip)
{
if (regularRun.StartPosition != null)
{
var glyphRun = new GlyphRun(glyphTypeface,
bidiLevel: 0,
isSideways: false,
renderingEmSize: FontSize,
pixelsPerDip: (float)pixelsPerDip,
glyphIndices: regularRun.GlyphIndexes,
baselineOrigin: new Point(Math.Round(regularRun.StartPosition.Value),
Math.Round(glyphTypeface.Baseline * FontSize + y)),
advanceWidths: regularRun.AdvanceWidths,
glyphOffsets: null,
characters: null,
deviceFontName: null,
clusterMap: null,
caretStops: null,
language: null);
drawingContext.DrawGlyphRun(brush, glyphRun);
}
}
And then some random code, which used the prior methods:
var regularRun = new GlyphRunInfo();
var selectionRun = new GlyphRunInfo();
// (...)
for (int ch = 0, index = line * Document.BytesPerRow; ch < charPositions.Count && index < availableBytes; ch++, index++)
{
byte drawnByte = dataBuffer[index];
char drawnChar = (drawnByte < 32 || drawnByte > 126) ? '.' : (char)drawnByte;
if (enteringMode == EnteringMode.Overwrite && (selection?.IsCharSelected(index + offset) ?? false))
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, selectionRun);
else
AddGlyph(drawnChar, charPositions[ch].Position.TextCharX, regularRun);
}
regularRun.FillMissingAdvanceWidths();
selectionRun.FillMissingAdvanceWidths();
DrawGlyphRun(drawingContext, regularRun, SystemColors.WindowTextBrush, linePositions[line].TextStartY, pixelsPerDip);
DrawGlyphRun(drawingContext, selectionRun, SystemColors.HighlightTextBrush, linePositions[line].TextStartY, pixelsPerDip);

WP7 Silverlight/XNA split

I want to make an app that has Silverlight menus but the game part of the app is XNA. I am trying to get the Silverlight/XNA split working using the code from this example game and the method of doing XNA rendering in Silverlight here. Combining these 2 tutorials I have source code that looks like this:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;
using Microsoft.Phone.Controls;
using System.Windows.Navigation;
namespace FYP
{
public partial class GamePage : PhoneApplicationPage
{
GameTimer timer;
SpriteBatch spriteBatch;
Texture2D ballTexture;
IList<Ball> balls = new List<Ball>();
bool touching = false;
public GamePage(ContentManager contentManager)
{
InitializeComponent();
//base.Initialize();
// Create a timer for this page
timer = new GameTimer();
timer.UpdateInterval = TimeSpan.FromTicks(333333);
//timer.Update += OnUpdate;
//timer.Draw += OnDraw;
// TODO: use this.Content to load your game content here
ballTexture = contentManager.Load<Texture2D>("Ball");
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
// Set the sharing mode of the graphics device to turn on XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(true);
timer.Start();
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(SharedGraphicsDeviceManager.Current.GraphicsDevice);
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
base.OnNavigatedFrom(e);
// Set the sharing mode of the graphics device to turn off XNA rendering
SharedGraphicsDeviceManager.Current.GraphicsDevice.SetSharingMode(false);
// Stop the timer
timer.Stop();
}
private void OnUpdate(GameTime gameTime)
{
// Allows the game to exit
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
//this.Exit();
// TODO: Add your update logic here
//base.Update(gameTime);
HandleTouches();
UpdateBalls();
}
private void OnDraw(GameTime gameTime)
{
SharedGraphicsDeviceManager.Current.GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.White);
// TODO: Add your drawing code here
foreach (Ball ball in balls)
{
ball.Draw(spriteBatch);
}
//base.Draw(gameTime);
}
private void HandleTouches()
{
TouchCollection touches = TouchPanel.GetState();
if (!touching && touches.Count > 0)
{
touching = true;
Random random = new Random(DateTime.Now.Millisecond);
Color ballColor = new Color(random.Next(255), random.Next(255), random.Next(255));
Vector2 velocity = new Vector2((random.NextDouble() > .5 ? -1 : 1) * random.Next(9), (random.NextDouble() > .5 ? -1 : 1) * random.Next(9)) + Vector2.UnitX + Vector2.UnitY;
//Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, (float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Height / 2);
Vector2 center = new Vector2((float)SharedGraphicsDeviceManager.Current.GraphicsDevice.Viewport.Width / 2, 200);
float radius = 25f * (float)random.NextDouble() + 5f;
balls.Add(new Ball(this, ballColor, ballTexture, center, velocity, radius));
}
else if (touches.Count == 0)
{
touching = false;
}
}
private void UpdateBalls()
{
foreach (Ball ball in balls)
{
ball.Update();
}
}
}
}
I do not understand how base works, I've had to comment out base.initialize, update and draw however base.OnNavigatedFrom works.
Also, should I be able to get my code to work in theory? I find it very complicated and although reading about XNA/Silverlight to be possible I can't find any source code where people have successfully combined XNA and Silverlight into the same app.
these videos will help you out to understand XNA and SilverLight/XNA combined platform
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11a-XNA-for-Windows-Phone--Part-1
http://channel9.msdn.com/Series/Mango-Jump-Start/Mango-Jump-Start-11b-XNA-for-Windows-Phone--Part-2
Theoretically XNA and SilverLight XNA Combined platform are pretty much the same, just a difference of bits and pieces, You can even ask XNA to render some SilverLight Control, which will make it easier to handle some button event in your game.
Hope this helps

How to set the location of WPF window to the bottom right corner of desktop?

I want to show my window on top of the TaskBar's clock when the windows starts.
How can I find the bottom right corner location of my desktop?
I use this code that works well in windows forms app but does not work correctly in WPF:
var desktopWorkingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
this.Left = desktopWorkingArea.Right - this.Width;
this.Top = desktopWorkingArea.Bottom - this.Height;
This code works for me in WPF both with Display 100% and 125%
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
this.Left = desktopWorkingArea.Right - this.Width;
this.Top = desktopWorkingArea.Bottom - this.Height;
}
In brief I use
System.Windows.SystemParameters.WorkArea
instead of
System.Windows.Forms.Screen.PrimaryScreen.WorkingArea
To access the desktop rectangle, you could use the Screen class - Screen.PrimaryScreen.WorkingArea property is the rectangle of your desktop.
Your WPF window has Top and Left properties as well as Width and Height, so you could set those properties relative to the desktop location.
You can use the window's SizeChanged event instead of Loaded if you want the window to stay in the corner when its size changes. This is especially handy if the window has Window.SizeToContent set to some value other than SizeToContent.Manual; in this case it will adjust to fit the content while staying in the corner.
public MyWindow()
{
SizeChanged += (o, e) =>
{
var r = SystemParameters.WorkArea;
Left = r.Right - ActualWidth;
Top = r.Bottom - ActualHeight;
};
InitializeComponent();
}
Note also that you should subtract ActualWidth and ActualHeight (instead of Width and Height as shown in some other replies) to handle more possible situations, for example switching between SizeToContent modes at runtime.
My code:
MainWindow.WindowStartupLocation = WindowStartupLocation.Manual;
MainWindow.Loaded += (s, a) =>
{
MainWindow.Height = SystemParameters.WorkArea.Height;
MainWindow.Width = SystemParameters.WorkArea.Width;
MainWindow.SetLeft(SystemParameters.WorkArea.Location.X);
MainWindow.SetTop(SystemParameters.WorkArea.Location.Y);
};
I solved this problem with a new window containing a label named MessageDisplay. The code accompanying the window was as follows:
public partial class StatusWindow : Window
{
static StatusWindow display;
public StatusWindow()
{
InitializeComponent();
}
static public void DisplayMessage( Window parent, string message )
{
if ( display != null )
ClearMessage();
display = new StatusWindow();
display.Top = parent.Top + 100;
display.Left = parent.Left + 10;
display.MessageDisplay.Content = message;
display.Show();
}
static public void ClearMessage()
{
display.Close();
display = null;
}
}
For my application, the setting of top and left puts this window below the menu on the main window (passed to DisplayMessage in the first parameter);
This above solutions did not entirely work for my window - it was too low and the bottom part of the window was beneath the taskbar and below the desktop workspace. I needed to set the position after the window content had been rendered:
private void Window_ContentRendered(object sender, EventArgs e)
{
var desktopWorkingArea = System.Windows.SystemParameters.WorkArea;
this.Left = desktopWorkingArea.Right - this.Width - 5;
this.Top = desktopWorkingArea.Bottom - this.Height - 5;
}
Also, part of the frame was out of view, so I had to adjust by 5. Not sure why this is needed in my situation.
#Klaus78 's answer is correct. But since this is first thing google pops up and if working in environments where screen resolution can change often such that your app runs on virtual desktops or virtual servers and you still need it to update its placement when the screen resolution changes I have found linking to the SystemEvents.DisplaySettingsChanged event to be beneficial. Here is an example using rx and you can put this in your constructor for your view.
Observable
.FromEventPattern<EventHandler, EventArgs>(_ => SystemEvents.DisplaySettingsChanged += _, _ => SystemEvents.DisplaySettingsChanged -= _)
.Select(_ => SystemParameters.WorkArea)
.Do(_ =>
{
Left = _.Right - Width;
Top = _.Bottom - Height;
})
.Subscribe();

Issues with XPSDocumentWriter and PrintDialog.PrintDocument

Our company is developing an application (WPF, targeted to .NET 3.5) with the WPF Diagramming Components from MindFusion.Apparently, printing and saving XPS Documents results in various errors on different systems.
I reduced the problem to a single sample XPS Document created from our application.I'll first give an overview of the concerned systems and break down the issues when respectively saving an xps document and printing the diagram visual using the new WPF Printing Path in the following list:
Note: All three systems have Windows XP SP3 with .NET 3.5 Framework SP1 installed.
Using the XpsDocumentWriter to write a XPS document with the Paginator:
PC 1 - The XPS Viewer (working with IE 7.0) doesn’t work (even after reinstall of .Net 3.5). XPS Viewer from the Essential Pack opens the document, but the view is completely blurred. But as you can see, our application on the right side of the screenshot uses a DocumentViewer to test this issue, which works correctly. Printing from the corrupted XPS Viewer results in the same output as on screen, while printing from the integrated print function in the DocumentViewer ( without intervention from our application) gives a blurry output which is a little more readable, but still not acceptable.
PC 2 - The IE XPS Viewer works correctly. The print ouput is inconsistent. Sometimes, the graphics (Shapes) are not complete, or the printing device notifies about lack of memory (with the same document).
PC 3 – The IE XPS Viewer works correctly, but initiating a print job always leads to this exception within IE itself.
Note: All heretofore mentioned issues have been tested with the XPS Document (already mentioned above) created by our application.
Creating a print job with PrintDialog.PrintDocument and the Paginator:
Printing from our application gives a consistent output with all system: the bigger the document (speaking in term of the page media size), the more blurry it gets. Unfortunately, a lot of potential causes have been already omitted.
The code for printing the document is fairly simple.
• Instead of using our own Paginator, I replaced the latter with another Paginator part of the MindFusion WPF Diagraming Components we use. I achieved the same result. (This statement is also true for XPSDocuments saved as file).
• I used the latest print driver available
• Changes to PrintTicket Resolution doesn’t seem to affect the ouput in any way
• Using another Visual instead of a diagram (like the Window of our Application itself) doesn’t affect the output
Due to these various issues, it seems that multiple causes are also probable. The previous exclusions lead me to assume that some crucial settings are missing in the PrintTicket, or something terribly wrong happens with the XPS to GDI Conversion behnd scenes. Apart from these assumptions, I'm running out of ideas.
Note: All print devices have non-XPS Drivers. HP Designjet 500, HP 2100
Last but not least, I serialised the same PrintTicket used for XPS Document file and the print job.
I would be thankful if anybody has experienced similar issues. Any suggestion is welcome.
I have currently working code but I am still having alignment issues. But print is not blurred I am giving you my code in hope that it may help you and we both could find a solution.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Controls;
using System.Data;
namespace VD
{
public class CustomPaginator : DocumentPaginator
{
private int _RowsPerPage;
private Size _PageSize;
private int _Rows;
public static DataTable dtToPrint;
public CustomPaginator()
{
}
public CustomPaginator(int rows, Size pageSize, DataTable dt)
{
_Rows = rows;
PageSize = pageSize;
dtToPrint = dt;
}
public override DocumentPage GetPage(int pageNumber)
{
int currentRow = _RowsPerPage * pageNumber;
var page = new PageElement(currentRow, Math.Min(_RowsPerPage, _Rows - currentRow))
{
Width = PageSize.Width,
Height = PageSize.Height,
};
page.Measure(PageSize);
page.Arrange(new Rect(new Point(0,0), PageSize));
return new DocumentPage(page);
}
public override bool IsPageCountValid
{ get { return true; } }
public override int PageCount
{ get { return (int)Math.Ceiling(_Rows / (double)_RowsPerPage); } }
public DataTable getDtProtols
{
get
{
return dtToPrint;
}
}
public override Size PageSize
{
get { return _PageSize; }
set
{
_PageSize = value;
_RowsPerPage = PageElement.RowsPerPage(PageSize.Height);
//Can't print anything if you can't fit a row on a page
Debug.Assert(_RowsPerPage > 0);
}
}
public override IDocumentPaginatorSource Source
{ get { return null; } }
}
public class PageElement : UserControl
{
private const int PageMargin = 75;
private const int HeaderHeight = 25;
private const int LineHeight = 20;
private const int ColumnWidth = 140;
private CustomPaginator objParent = new CustomPaginator();
private DataTable ProtocolsDT = null;
private int _CurrentRow;
private int _Rows;
public PageElement(int currentRow, int rows)
{
Margin = new Thickness(PageMargin);
_CurrentRow = currentRow;
_Rows = rows;
}
private static FormattedText MakeText(string text)
{
return new FormattedText(text, CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Tahoma"), 14, Brushes.Black);
}
public static int RowsPerPage(double height)
{
return (int)Math.Floor((height - (2 * PageMargin)
- HeaderHeight) / LineHeight);
}
protected override void OnRender(DrawingContext dc)
{
ProtocolsDT = objParent.getDtProtols;
Point curPoint = new Point(0, 0);
dc.DrawText(MakeText("Host Names"),curPoint );
curPoint.X += ColumnWidth;
for (int i = 1; i < ProtocolsDT.Columns.Count; i++)
{
dc.DrawText(MakeText(ProtocolsDT.Columns[i].ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.X = 0;
curPoint.Y += LineHeight;
dc.DrawRectangle(Brushes.Black, null, new Rect(curPoint, new Size(Width, 2)));
curPoint.Y += HeaderHeight - LineHeight;
Random numberGen = new Random();
//for (int i = _CurrentRow; i < _CurrentRow + _Rows; i++)
//{
// dc.DrawText(MakeText(ProtocolsDT.Rows[i][0].ToString()), curPoint);
//curPoint.X += ColumnWidth;
for (int j = 0; j < ProtocolsDT.Rows.Count; j++)
{
for (int z = 0; z < ProtocolsDT.Rows[j].ItemArray.Length; z++)
{
dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[z].ToString()), curPoint);
curPoint.X += ColumnWidth;
}
curPoint.Y += LineHeight;
curPoint.X = 0;
//dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[1].ToString()), curPoint);
//curPoint.X += ColumnWidth;
//dc.DrawText(MakeText(ProtocolsDT.Rows[j].ItemArray[2].ToString()), curPoint);
//curPoint.X += ColumnWidth;
//}
//curPoint.Y += LineHeight;
//curPoint.X = 0;
}
}
}
}
Another Class
private void PrintDataTable(DataTable dt)
{
var printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
var paginator = new CustomPaginator(dt.Rows.Count,
new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight),dt);
try
{
printDialog.PrintDocument(paginator, "Running Protocols Report");
}
catch (Exception ex)
{
MessageBox.Show(this, "Unable to print protocol information. Please check your printer settings.", "VD", MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK);
}
}
}

How can I show scrollbars on a System.Windows.Forms.TextBox only when the text doesn't fit?

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

Resources