|
//------------------------------------------------------------------------------
// <copyright file="ToolBarButton.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
/*
*/
namespace System.Windows.Forms {
using System.Runtime.Serialization.Formatters;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System;
using System.Drawing;
using System.Text;
using System.Drawing.Design;
using Marshal = System.Runtime.InteropServices.Marshal;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Globalization;
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton"]/*' />
/// <devdoc>
/// <para>
/// Represents a Windows toolbar button.</para>
/// </devdoc>
[
Designer("System.Windows.Forms.Design.ToolBarButtonDesigner, " + AssemblyRef.SystemDesign),
DefaultProperty("Text"),
ToolboxItem(false),
DesignTimeVisible(false),
]
public class ToolBarButton : Component {
string text;
string name = null;
string tooltipText;
bool enabled = true;
bool visible = true;
bool pushed = false;
bool partialPush = false;
private int commandId = -1; // the cached command id of the button.
private ToolBarButtonImageIndexer imageIndexer;
ToolBarButtonStyle style = ToolBarButtonStyle.PushButton;
object userData;
// These variables below are used by the ToolBar control to help
// it manage some information about us.
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.stringIndex"]/*' />
/// <devdoc>
/// If this button has a string, what it's index is in the ToolBar's
/// internal list of strings. Needs to be package protected.
/// </devdoc>
/// <internalonly/>
internal IntPtr stringIndex = (IntPtr)(-1);
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.parent"]/*' />
/// <devdoc>
/// Our parent ToolBar control.
/// </devdoc>
/// <internalonly/>
internal ToolBar parent;
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.dropDownMenu"]/*' />
/// <devdoc>
/// For DropDown buttons, we can optionally show a
/// context menu when the button is dropped down.
/// </devdoc>
internal Menu dropDownMenu = null;
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ToolBarButton"]/*' />
/// <devdoc>
/// <para>Initializes a new instance of the <see cref='System.Windows.Forms.ToolBarButton'/> class.</para>
/// </devdoc>
public ToolBarButton() {
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ToolBarButton1"]/*' />
/// <devdoc>
/// <para>[To be supplied.]</para>
/// </devdoc>
public ToolBarButton(string text) : base() {
this.Text = text;
}
// We need a special way to defer to the ToolBar's image
// list for indexing purposes.
internal class ToolBarButtonImageIndexer : ImageList.Indexer {
private ToolBarButton owner;
public ToolBarButtonImageIndexer(ToolBarButton button) {
owner = button;
}
public override ImageList ImageList {
get {
if ((owner != null) && (owner.parent != null)) {
return owner.parent.ImageList;
}
return null;
}
set { Debug.Assert(false, "We should never set the image list"); }
}
}
internal ToolBarButtonImageIndexer ImageIndexer {
get {
if (imageIndexer == null) {
imageIndexer = new ToolBarButtonImageIndexer(this);
}
return imageIndexer;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.DropDownMenu"]/*' />
/// <devdoc>
/// <para>
/// Indicates the menu to be displayed in
/// the drop-down toolbar button.</para>
/// </devdoc>
[
DefaultValue(null),
TypeConverterAttribute(typeof(ReferenceConverter)),
SRDescription(SR.ToolBarButtonMenuDescr)
]
public Menu DropDownMenu {
get {
return dropDownMenu;
}
set {
//The dropdownmenu must be of type ContextMenu, Main & Items are invalid.
//
if (value != null && !(value is ContextMenu)) {
throw new ArgumentException(SR.GetString(SR.ToolBarButtonInvalidDropDownMenuType));
}
dropDownMenu = value;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Enabled"]/*' />
/// <devdoc>
/// <para>Indicates whether the button is enabled or not.</para>
/// </devdoc>
[
DefaultValue(true),
Localizable(true),
SRDescription(SR.ToolBarButtonEnabledDescr)
]
public bool Enabled {
get {
return enabled;
}
set {
if (enabled != value) {
enabled = value;
if (parent != null && parent.IsHandleCreated) {
parent.SendMessage(NativeMethods.TB_ENABLEBUTTON, FindButtonIndex(),
enabled ? 1 : 0);
}
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ImageIndex"]/*' />
/// <devdoc>
/// <para> Indicates the index
/// value of the image assigned to the button.</para>
/// </devdoc>
[
TypeConverterAttribute(typeof(ImageIndexConverter)),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
DefaultValue(-1),
RefreshProperties(RefreshProperties.Repaint),
Localizable(true),
SRDescription(SR.ToolBarButtonImageIndexDescr)
]
public int ImageIndex {
get {
return ImageIndexer.Index;
}
set {
if (ImageIndexer.Index != value) {
if (value < -1)
throw new ArgumentOutOfRangeException("ImageIndex", SR.GetString(SR.InvalidLowBoundArgumentEx, "ImageIndex", (value).ToString(CultureInfo.CurrentCulture), -1));
ImageIndexer.Index = value;
UpdateButton(false);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ImageIndex"]/*' />
/// <devdoc>
/// <para> Indicates the index
/// value of the image assigned to the button.</para>
/// </devdoc>
[
TypeConverterAttribute(typeof(ImageKeyConverter)),
Editor("System.Windows.Forms.Design.ImageIndexEditor, " + AssemblyRef.SystemDesign, typeof(UITypeEditor)),
DefaultValue(""),
Localizable(true),
RefreshProperties(RefreshProperties.Repaint),
SRDescription(SR.ToolBarButtonImageIndexDescr)
]
public string ImageKey {
get {
return ImageIndexer.Key;
}
set {
if (ImageIndexer.Key != value) {
ImageIndexer.Key = value;
UpdateButton(false);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Name"]/*' />
/// <devdoc>
/// Name of this control. The designer will set this to the same
/// as the programatic Id "(name)" of the control - however this
/// property has no bearing on the runtime aspects of this control.
/// </devdoc>
[Browsable(false)]
public string Name {
get {
return WindowsFormsUtils.GetComponentName(this, name);
}
set {
if (value == null || value.Length == 0) {
name = null;
}
else {
name = value;
}
if(Site!= null) {
Site.Name = name;
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Parent"]/*' />
/// <devdoc>
/// <para>Indicates the toolbar control that the toolbar button is assigned to. This property is
/// read-only.</para>
/// </devdoc>
[
Browsable(false),
]
public ToolBar Parent {
get {
return parent;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.PartialPush"]/*' />
/// <devdoc>
/// <para>
/// Indicates whether a toggle-style toolbar button
/// is partially pushed.</para>
/// </devdoc>
[
DefaultValue(false),
SRDescription(SR.ToolBarButtonPartialPushDescr)
]
public bool PartialPush {
get {
if (parent == null || !parent.IsHandleCreated)
return partialPush;
else {
if ((int)parent.SendMessage(NativeMethods.TB_ISBUTTONINDETERMINATE, FindButtonIndex(), 0) != 0)
partialPush = true;
else
partialPush = false;
return partialPush;
}
}
set {
if (partialPush != value) {
partialPush = value;
UpdateButton(false);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Pushed"]/*' />
/// <devdoc>
/// <para>Indicates whether a toggle-style toolbar button is currently in the pushed state.</para>
/// </devdoc>
[
DefaultValue(false),
SRDescription(SR.ToolBarButtonPushedDescr)
]
public bool Pushed {
get {
if (parent == null || !parent.IsHandleCreated)
return pushed;
else {
return GetPushedState();
}
}
set {
if (value != Pushed) { // Getting property Pushed updates pushed member variable
pushed = value;
UpdateButton(false, false, false);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Rectangle"]/*' />
/// <devdoc>
/// <para>Indicates the bounding rectangle for a toolbar button. This property is
/// read-only.</para>
/// </devdoc>
public Rectangle Rectangle {
get {
if (parent != null) {
NativeMethods.RECT rc = new NativeMethods.RECT();
UnsafeNativeMethods.SendMessage(new HandleRef(parent, parent.Handle), NativeMethods.TB_GETRECT, FindButtonIndex(), ref rc);
return Rectangle.FromLTRB(rc.left, rc.top, rc.right, rc.bottom);
}
return Rectangle.Empty;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Style"]/*' />
/// <devdoc>
/// <para> Indicates the style of the
/// toolbar button.</para>
/// </devdoc>
[
DefaultValue(ToolBarButtonStyle.PushButton),
SRDescription(SR.ToolBarButtonStyleDescr),
RefreshProperties(RefreshProperties.Repaint)
]
public ToolBarButtonStyle Style {
get {
return style;
}
set {
//valid values are 0x1 to 0x4
if (!ClientUtils.IsEnumValid(value, (int)value, (int)ToolBarButtonStyle.PushButton, (int)ToolBarButtonStyle.DropDownButton)){
throw new InvalidEnumArgumentException("value", (int)value, typeof(ToolBarButtonStyle));
}
if (style == value) return;
style = value;
UpdateButton(true);
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Tag"]/*' />
[
SRCategory(SR.CatData),
Localizable(false),
Bindable(true),
SRDescription(SR.ControlTagDescr),
DefaultValue(null),
TypeConverter(typeof(StringConverter)),
]
public object Tag {
get {
return userData;
}
set {
userData = value;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Text"]/*' />
/// <devdoc>
/// <para> Indicates the text that is displayed on the toolbar button.</para>
/// </devdoc>
[
Localizable(true),
DefaultValue(""),
SRDescription(SR.ToolBarButtonTextDescr)
]
public string Text {
get {
return(text == null) ? "" : text;
}
set {
if (String.IsNullOrEmpty(value)) {
value = null;
}
if ( (value == null && text != null) ||
(value != null && (text == null || !text.Equals(value)))) {
text = value;
// 94943 VSWhidbey Adding a mnemonic requires a handle recreate.
UpdateButton(WindowsFormsUtils.ContainsMnemonic(text), true, true);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ToolTipText"]/*' />
/// <devdoc>
/// <para>
/// Indicates
/// the text that appears as a tool tip for a control.</para>
/// </devdoc>
[
Localizable(true),
DefaultValue(""),
SRDescription(SR.ToolBarButtonToolTipTextDescr)
]
public string ToolTipText {
get {
return tooltipText == null ? "" : tooltipText;
}
set {
tooltipText = value;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Visible"]/*' />
/// <devdoc>
/// <para>
/// Indicates whether the toolbar button
/// is visible.</para>
/// </devdoc>
[
DefaultValue(true),
Localizable(true),
SRDescription(SR.ToolBarButtonVisibleDescr)
]
public bool Visible {
get {
return visible;
}
set {
if (visible != value) {
visible = value;
UpdateButton(false);
}
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Width"]/*' />
/// <devdoc>
/// This is somewhat nasty -- by default, the windows ToolBar isn't very
/// clever about setting the width of buttons, and has a very primitive
/// algorithm that doesn't include for things like drop down arrows, etc.
/// We need to do a bunch of work here to get all the widths correct. Ugh.
/// </devdoc>
/// <internalonly/>
internal short Width {
get {
Debug.Assert(parent != null, "Parent should be non-null when button width is requested");
int width = 0;
ToolBarButtonStyle style = Style;
Size edge = SystemInformation.Border3DSize;
if (style != ToolBarButtonStyle.Separator) {
// COMPAT: this will force handle creation.
// we could use the measurement graphics, but it looks like this has been like this since Everett.
using (Graphics g = this.parent.CreateGraphicsInternal()) {
Size buttonSize = this.parent.buttonSize;
if (!(buttonSize.IsEmpty)) {
width = buttonSize.Width;
}
else {
if (this.parent.ImageList != null || !String.IsNullOrEmpty(Text)) {
Size imageSize = this.parent.ImageSize;
Size textSize = Size.Ceiling(g.MeasureString(Text, parent.Font));
if (this.parent.TextAlign == ToolBarTextAlign.Right) {
if (textSize.Width == 0)
width = imageSize.Width + edge.Width * 4;
else
width = imageSize.Width + textSize.Width + edge.Width * 6;
}
else {
if (imageSize.Width > textSize.Width)
width = imageSize.Width + edge.Width * 4;
else
width = textSize.Width + edge.Width * 4;
}
if (style == ToolBarButtonStyle.DropDownButton && this.parent.DropDownArrows) {
width += ToolBar.DDARROW_WIDTH;
}
}
else
width = this.parent.ButtonSize.Width;
}
}
}
else {
width = edge.Width * 2;
}
return(short)width;
}
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.Dispose"]/*' />
/// <internalonly/>
/// <devdoc>
/// </devdoc>
protected override void Dispose(bool disposing) {
if (disposing) {
if (parent != null) {
int index = FindButtonIndex();
if (index != -1) {
parent.Buttons.RemoveAt(index);
}
}
}
base.Dispose(disposing);
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.FindButtonIndex"]/*' />
/// <devdoc>
/// Finds out index in the parent.
/// </devdoc>
/// <internalonly/>
private int FindButtonIndex() {
for (int x = 0; x < parent.Buttons.Count; x++) {
if (parent.Buttons[x] == this) {
return x;
}
}
return -1;
}
// VSWhidbey 177016: This is necessary to get the width of the buttons in the toolbar,
// including the width of separators, so that we can accurately position the tooltip adjacent
// to the currently hot button when the user uses keyboard navigation to access the toolbar.
internal int GetButtonWidth() {
// Assume that this button is the same width as the parent's ButtonSize's Width
int buttonWidth = Parent.ButtonSize.Width;
NativeMethods.TBBUTTONINFO button = new NativeMethods.TBBUTTONINFO();
button.cbSize = Marshal.SizeOf(typeof(NativeMethods.TBBUTTONINFO));
button.dwMask = NativeMethods.TBIF_SIZE;
int buttonID = (int)UnsafeNativeMethods.SendMessage(new HandleRef(Parent, Parent.Handle), NativeMethods.TB_GETBUTTONINFO, commandId, ref button);
if (buttonID != -1) {
buttonWidth = button.cx;
}
return buttonWidth;
}
private bool GetPushedState()
{
if ((int)parent.SendMessage(NativeMethods.TB_ISBUTTONCHECKED, FindButtonIndex(), 0) != 0) {
pushed = true;
}
else {
pushed = false;
}
return pushed;
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.GetTBBUTTON"]/*' />
/// <devdoc>
/// Returns a TBBUTTON object that represents this ToolBarButton.
/// </devdoc>
internal NativeMethods.TBBUTTON GetTBBUTTON(int commandId) {
NativeMethods.TBBUTTON button = new NativeMethods.TBBUTTON();
button.iBitmap = ImageIndexer.ActualIndex;
// set up the state of the button
//
button.fsState = 0;
if (enabled) button.fsState |= NativeMethods.TBSTATE_ENABLED;
if (partialPush && style == ToolBarButtonStyle.ToggleButton) button.fsState |= NativeMethods.TBSTATE_INDETERMINATE;
if (pushed) button.fsState |= NativeMethods.TBSTATE_CHECKED;
if (!visible) button.fsState |= NativeMethods.TBSTATE_HIDDEN;
// set the button style
//
switch (style) {
case ToolBarButtonStyle.PushButton:
button.fsStyle = NativeMethods.TBSTYLE_BUTTON;
break;
case ToolBarButtonStyle.ToggleButton:
button.fsStyle = NativeMethods.TBSTYLE_CHECK;
break;
case ToolBarButtonStyle.Separator:
button.fsStyle = NativeMethods.TBSTYLE_SEP;
break;
case ToolBarButtonStyle.DropDownButton:
button.fsStyle = NativeMethods.TBSTYLE_DROPDOWN;
break;
}
button.dwData = (IntPtr)0;
button.iString = this.stringIndex;
this.commandId = commandId;
button.idCommand = commandId;
return button;
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.GetTBBUTTONINFO"]/*' />
/// <devdoc>
/// Returns a TBBUTTONINFO object that represents this ToolBarButton.
/// </devdoc>
internal NativeMethods.TBBUTTONINFO GetTBBUTTONINFO(bool updateText, int newCommandId) {
NativeMethods.TBBUTTONINFO button = new NativeMethods.TBBUTTONINFO();
button.cbSize = Marshal.SizeOf(typeof(NativeMethods.TBBUTTONINFO));
button.dwMask = NativeMethods.TBIF_IMAGE
| NativeMethods.TBIF_STATE | NativeMethods.TBIF_STYLE;
// Comctl on Win98 interprets null strings as empty strings, which forces
// the button to leave space for text. The only workaround is to avoid having comctl
// update the text.
if (updateText) {
button.dwMask |= NativeMethods.TBIF_TEXT;
}
button.iImage = ImageIndexer.ActualIndex;
if (newCommandId != commandId) {
commandId = newCommandId;
button.idCommand = newCommandId;
button.dwMask |= NativeMethods.TBIF_COMMAND;
}
// set up the state of the button
//
button.fsState = 0;
if (enabled) button.fsState |= NativeMethods.TBSTATE_ENABLED;
if (partialPush && style == ToolBarButtonStyle.ToggleButton) button.fsState |= NativeMethods.TBSTATE_INDETERMINATE;
if (pushed) button.fsState |= NativeMethods.TBSTATE_CHECKED;
if (!visible) button.fsState |= NativeMethods.TBSTATE_HIDDEN;
// set the button style
//
switch (style) {
case ToolBarButtonStyle.PushButton:
button.fsStyle = NativeMethods.TBSTYLE_BUTTON;
break;
case ToolBarButtonStyle.ToggleButton:
button.fsStyle = NativeMethods.TBSTYLE_CHECK;
break;
case ToolBarButtonStyle.Separator:
button.fsStyle = NativeMethods.TBSTYLE_SEP;
break;
}
if (text == null) {
button.pszText = Marshal.StringToHGlobalAuto("\0\0");
}
else {
string textValue = this.text;
PrefixAmpersands(ref textValue);
button.pszText = Marshal.StringToHGlobalAuto(textValue);
}
return button;
}
private void PrefixAmpersands(ref string value) {
// Due to a comctl32 problem, ampersands underline the next letter in the
// text string, but the accelerators don't work.
// So in this function, we prefix ampersands with another ampersand
// so that they actually appear as ampersands.
//
// Sanity check parameter
//
if (value == null || value.Length == 0) {
return;
}
// If there are no ampersands, we don't need to do anything here
//
if (value.IndexOf('&') < 0) {
return;
}
// Insert extra ampersands
//
StringBuilder newString = new StringBuilder();
for(int i=0; i < value.Length; ++i) {
if (value[i] == '&') {
if (i < value.Length - 1 && value[i+1] == '&') {
++i; // Skip the second ampersand
}
newString.Append("&&");
}
else {
newString.Append(value[i]);
}
}
value = newString.ToString();
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.ToString"]/*' />
/// <devdoc>
/// </devdoc>
/// <internalonly/>
public override string ToString() {
return "ToolBarButton: " + Text + ", Style: " + Style.ToString("G");
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.UpdateButton"]/*' />
/// <devdoc>
/// When a button property changes and the parent control is created,
/// we need to make sure it gets the new button information.
/// If Text was changed, call the next overload.
/// </devdoc>
internal void UpdateButton(bool recreate) {
UpdateButton(recreate, false, true);
}
/// <include file='doc\ToolBarButton.uex' path='docs/doc[@for="ToolBarButton.UpdateButton1"]/*' />
/// <devdoc>
/// When a button property changes and the parent control is created,
/// we need to make sure it gets the new button information.
/// </devdoc>
private void UpdateButton(bool recreate, bool updateText, bool updatePushedState) {
// It looks like ToolBarButtons with a DropDownButton tend to
// lose the DropDownButton very easily - so we need to recreate
// the button each time it changes just to be sure.
//
if (style == ToolBarButtonStyle.DropDownButton && parent != null && parent.DropDownArrows) {
recreate = true;
}
// we just need to get the Pushed state : this asks the Button its states and sets
// the private member "pushed" to right value..
// this member is used in "InternalSetButton" which calls GetTBBUTTONINFO(bool updateText)
// the GetButtonInfo method uses the "pushed" variable..
//rather than setting it ourselves .... we asks the button to set it for us..
if (updatePushedState && parent != null && parent.IsHandleCreated) {
GetPushedState();
}
if (parent != null) {
int index = FindButtonIndex();
if (index != -1)
parent.InternalSetButton(index, this, recreate, updateText);
}
}
}
}
|