//---------------------------------------------------------------------
// This file is part of the Microsoft .NET Framework SDK Code Samples.
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//This source code is intended only as a supplement to Microsoft
//Development Tools and/or on-line documentation. See these other
//materials for detailed information regarding Microsoft code samples.
//
//THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//PARTICULAR PURPOSE.
//---------------------------------------------------------------------
using System;
using System.Drawing;
using System.Windows.Forms;
//using Microsoft.WindowsCE.Forms;
using System.Globalization;
namespace Microsoft.Samples
{
public class RotatedText : Form
{
const int CONTROL_HEIGHT = 22;
Button drawButton;
NumericUpDown spokesNumericUpDown;
FontwhirlControl whirlControl;
public RotatedText()
{
// Suspend control layout for better performance and to suspend
// automatic scaling until we've set the AutoScaleDimensions and
// AutoScaleMode
this.SuspendLayout();
// note: controls use the Dock property to automatically
// reposition the controls when a device's screen orientation
// changes
this.whirlControl = new FontwhirlControl();
this.whirlControl.BackColor = Color.LawnGreen;
this.whirlControl.Dock = DockStyle.Fill;
this.whirlControl.Parent = this;
this.whirlControl.Text = "abc ABC 123";
Panel controlsPanel = new Panel();
controlsPanel.Height = CONTROL_HEIGHT;
controlsPanel.Dock = DockStyle.Bottom;
controlsPanel.Parent = this;
this.drawButton = new Button();
this.drawButton.Text = "Draw";
this.drawButton.Parent = controlsPanel;
this.drawButton.Width = 60;
this.drawButton.Dock = DockStyle.Right;
this.drawButton.Click += new EventHandler(this.DrawButton_Click);
this.spokesNumericUpDown = new NumericUpDown();
this.spokesNumericUpDown.Parent = controlsPanel;
this.spokesNumericUpDown.Width = 50;
this.spokesNumericUpDown.Maximum = 50;
this.spokesNumericUpDown.Minimum = 1;
this.spokesNumericUpDown.Value = 10;
this.spokesNumericUpDown.Dock = DockStyle.Left;
Label label = new Label();
label.Parent = controlsPanel;
label.Width = 115;
label.Text = "Number of Spokes:";
label.Dock = DockStyle.Left;
// put (ok) button on form to close it when done with this sample
this.MinimizeBox = false;
// dpi settings for the screen the form was initially designed on
this.AutoScaleDimensions = new SizeF(96f, 96f);
// automatically scale controls based on the DPI of the screen
this.AutoScaleMode = AutoScaleMode.Dpi;
// show current dpi of screen
this.Text = string.Format(CultureInfo.InvariantCulture,
"RotatedText({0}dpi)",
new object[] { this.CurrentAutoScaleDimensions.Height });
// Now that we've set the AutoScaleMode, call ResumeLayout() to
// do the actual scaling
this.ResumeLayout();
// draw default image
DrawButton_Click(null, EventArgs.Empty);
}
///
/// When someone clicks the Draw button
/// a) set the number of spokes from the numericUpDown control
/// b) instruct whirl control to render the rotated text
///
void DrawButton_Click(object o, EventArgs e)
{
this.whirlControl.Spokes = (int)this.spokesNumericUpDown.Value;
this.whirlControl.DrawText();
}
static void Main()
{
Application.Run(new RotatedText());
}
}
///
/// Control to draw rotated text. By rendering the rotated text in a
/// separate control we can take advantage of docking and anchoring
/// to resize the control and make the application respond to screen
/// orientation changes.
///
public class FontwhirlControl : Control
{
const int POINTSPERINCH = 96; // initial dpi value used when designed
int spokesValue = 10;
Bitmap offScreenBitmap = null;
public FontwhirlControl()
{
}
///
/// property specifying number of text spokes to draw
///
public int Spokes
{
get
{
return this.spokesValue;
}
set
{
this.spokesValue = value;
}
}
///
/// when the Resize() event fires, make a new off screen bitmap
/// to draw into.
///
void ResizeDrawingBuffer()
{
Bitmap newBitmap = new Bitmap(this.ClientSize.Width,
this.ClientSize.Height);
using (Graphics g = Graphics.FromImage(newBitmap))
g.Clear(this.BackColor);
// copy the old offScreenBitmap into the new buffer
if(this.offScreenBitmap != null)
{
using (Graphics g = Graphics.FromImage(newBitmap))
g.DrawImage(this.offScreenBitmap, 0, 0);
// Graphics objects are not garbage collected and need to be
// explicitly disposed
this.offScreenBitmap.Dispose();
}
this.offScreenBitmap = newBitmap;
}
///
/// Create the rotated font using a LOGFONT structure. Important
/// notes:
/// a) rotation angle is in 1/10's of a degree
/// b) orientation and escapement values are the same on
/// mobile devices.
///
Font CreateRotatedFont(int angle, Graphics g)
{
LogFont lf = new LogFont();
// scale a 12 point font for current screen DPI
lf.Height = (int)(-16f * g.DpiY / POINTSPERINCH);
lf.Width = 0;
// rotation angle in tenths of degrees
lf.Escapement = angle * 10;
// Orientation == Escapement for mobile device OS
lf.Orientation = lf.Escapement;
lf.Weight = 0;
lf.Italic = 0;
lf.Underline = 0;
lf.StrikeOut = 0;
lf.CharSet = LogFontCharSet.Default;
lf.OutPrecision = LogFontPrecision.Default;
lf.ClipPrecision = LogFontClipPrecision.Default;
lf.Quality = LogFontQuality.ClearType;
lf.PitchAndFamily = LogFontPitchAndFamily.Default;
lf.FaceName = "Tahoma";
return Font.FromLogFont(lf);
}
///
/// Draw rotated text spokes into an off screen bitmap which will
/// be drawn to the screen via the OnPaint() event.
///
public void DrawText()
{
using (Graphics g = Graphics.FromImage(this.offScreenBitmap))
{
g.Clear(this.BackColor);
float fontAngle;
Font rotatedFont;
for(int spoke = 0; spoke < this.spokesValue; spoke++)
{
// calculate the rotation angle and create a rotated font
fontAngle = (float)spoke * (360f / (float)this.spokesValue);
rotatedFont = CreateRotatedFont((int)fontAngle, g);
// Draw the rotated text starting from the center of the
// off screen bitmap (essentially, the client area of the
// custom control).
g.DrawString(this.Text,
rotatedFont,
new SolidBrush(this.ForeColor),
this.offScreenBitmap.Width / 2,
this.offScreenBitmap.Height / 2,
new StringFormat(StringFormatFlags.NoWrap |
StringFormatFlags.NoClip));
// Graphics objects are not garbage collected and need to be
// explicitly disposed
rotatedFont.Dispose();
}
}
// cause the control to re-paint itself
this.Invalidate();
}
// override this to do nothing. Allowing the background to paint would
// cause the control to flicker when it repaints. And, it's not
// necessary since we draw the whole off screen bitmap during a paint
// event.
protected override void OnPaintBackground(PaintEventArgs e)
{
}
///
/// Draw the off screen bitmap that contains the rotated text spokes
///
protected override void OnPaint(PaintEventArgs e)
{
if(this.offScreenBitmap == null)
return;
e.Graphics.DrawImage(this.offScreenBitmap, 0, 0);
}
///
/// Resize event fires when screen orientation changes. When this
/// happens, resize the off screen bitmap and redraw the rotated
/// text spokes.
///
protected override void OnResize(EventArgs e)
{
if(this.ClientSize.Width <= 0 || this.ClientSize.Height <= 0)
return;
ResizeDrawingBuffer();
DrawText();
}
}
}