I made a subclass for TextBox and tested the following method in a separate test project.
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
The code Works well, but in my subclass it does not. The above static method is called only from the method UpdateVisibleScrollBars, which is called only in the following places:
from the subclass' c-tor
OnTextChanged
OnFontChanged
OnResize
The only speciality of this subclass is that it has a placeholder when the user did not enter anything in the TextBox, and this UpdateVisibleScrollBars. In this subclass NumberOfPhysicalLinesInTextBox does not return, it loops indefinitely because the GetFirstCharIndexFromLine always returns 0 when the text is the placeholder: "Enter text here...".
Update: I do not use Lines because I need the physical lines (the lines that result after Word-wrapping), so I can know if I need to show or hide the vertical scrollbar. The TextBox is set with WordWrap = true. Here is the GetFirstCharIndexFromLine method's official documentation.
Update 2: All the class' code is below (without non-English comments):
class EnhancedTextBox : TextBox
{
internal string PlaceholderText = "Enter text here...";
internal string ActualText
{
get
{
return PlaceholderShown ? "" : Text;
}
set
{
if (value == "" || value == null)
{
if (Text == PlaceholderText)
{
PlaceholderShown = true;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
else
{
if (!Focused)
{
BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
else
{
PlaceholderShown = false;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
}
}
else
{
if (Text != value)
{
BeforeActualTextChanged?.Invoke(this, EventArgs.Empty);
ProgrammaticTextChange = true;
Text = value;
ProgrammaticTextChange = false;
}
PlaceholderShown = false;
ActualTextChanged?.Invoke(this, EventArgs.Empty);
}
}
}
internal Color _PlaceholderForeColor = Utils.GrayByPercent(50);
internal Color PlaceholderForeColor
{
get
{
return _PlaceholderForeColor;
}
set
{
if (_PlaceholderForeColor != value)
{
_PlaceholderForeColor = value;
Invalidate();
}
}
}
internal Color _NormalForeColor = Color.Empty;
internal Color NormalForeColor
{
get
{
return _NormalForeColor;
}
set
{
if (_NormalForeColor != value)
{
_NormalForeColor = value;
Invalidate();
}
}
}
internal bool _PlaceholderShown = true;
internal bool PlaceholderShown
{
get
{
return _PlaceholderShown;
}
set
{
if (_PlaceholderShown != value)
{
_PlaceholderShown = value;
ForceUpdatePlaceholderShown(value);
}
}
}
internal void ForceUpdatePlaceholderShown(bool value)
{
ForeColor = value ? PlaceholderForeColor :
NormalForeColor;
Invalidate();
}
public EnhancedTextBox() : base()
{
NormalForeColor = ForeColor;
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
ForceUpdatePlaceholderShown(true);
UpdateVisibleScrollBars();
}
protected override void OnEnter(EventArgs e)
{
HidePlaceholder();
base.OnEnter(e);
}
private void HidePlaceholder()
{
if (PlaceholderShown)
{
ProgrammaticTextChange = true;
Text = "";
ProgrammaticTextChange = false;
PlaceholderShown = false;
}
}
protected override void OnLeave(EventArgs e)
{
ShowPlaceholder();
base.OnLeave(e);
}
private void ShowPlaceholder()
{
if (Text == "")
{
ProgrammaticTextChange = true;
Text = PlaceholderText;
ProgrammaticTextChange = false;
PlaceholderShown = true;
}
}
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
internal bool ProgrammaticTextChange = false;
/// <summary>
/// Do not use this event using handlers. Use ActualTextChanged instead.
/// </summary>
/// <param name="e"></param>
protected override void OnTextChanged(EventArgs e)
{
if (ProgrammaticTextChange)
{
return;
}
ActualText = Text;
base.OnTextChanged(e);
UpdateVisibleScrollBars();
}
private bool busy = false;
private void UpdateVisibleScrollBars()
{
if (busy) return;
busy = true;
bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;
if (c1 && c2)
{
ScrollBars = ScrollBars.Both;
}
else if (c1)
{
ScrollBars = ScrollBars.Horizontal;
}
else if (c2)
{
ScrollBars = ScrollBars.Vertical;
}
else
{
ScrollBars = ScrollBars.None;
}
ScrollToCaret();
busy = false;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateVisibleScrollBars();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
UpdateVisibleScrollBars();
}
public event EventHandler ActualTextChanged, BeforeActualTextChanged;
}
If I replace bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2; with bool c2 = false; there is no non-ending while loop, although I see that the OnResize handler is called often in debugging with a breakpoint put on the c2 line, and repeatedly clicking Continue. Then If I press Continue really fast a few times, the program starts and is usable.
Update 3: Commenting out the UpdateVisibleScrollBars call inside the OnResize handler makes everything work. How can I make the scrollbars' visibility be changed when the TextBox is just resized?
Current code:
class EnhancedTextBox : TextBox
{
internal string _PlaceholderText = "Enter text here...";
internal string PlaceholderText
{
get
{
return _PlaceholderText;
}
set
{
_PlaceholderText = value;
Invalidate();
}
}
internal Color _PlaceholderForeColor = SystemColors.GrayText;
public Color PlaceholderForeColor
{
get
{
return _PlaceholderForeColor;
}
set
{
_PlaceholderForeColor = value;
Invalidate();
}
}
[Obsolete]
internal string ActualText
{
get
{
return Text;
}
set
{
if (Text != value)
{
Text = value;
}
}
}
internal Color _NormalForeColor = Color.Empty;
internal Color NormalForeColor
{
get
{
return _NormalForeColor;
}
set
{
if (_NormalForeColor != value)
{
_NormalForeColor = value;
ForeColor = value;
}
}
}
public EnhancedTextBox() : base()
{
NormalForeColor = ForeColor;
WordWrap = true;
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == 0xf)
{
if (!this.Focused && string.IsNullOrEmpty(this.Text)
&& !string.IsNullOrEmpty(this.PlaceholderText))
{
using (var g = this.CreateGraphics())
{
TextRenderer.DrawText(g, this.PlaceholderText, this.Font,
this.ClientRectangle, this.PlaceholderForeColor, this.BackColor,
TextFormatFlags.Top | TextFormatFlags.Left);
}
}
}
}
internal static int NumberOfPhysicalLinesInTextBox(TextBox tb)
{
int lc = 0;
while (tb.GetFirstCharIndexFromLine(lc) != -1)
{
++lc;
}
return lc;
}
internal bool ProgrammaticTextChange = false;
protected override void OnTextChanged(EventArgs e)
{
base.OnTextChanged(e);
ActualTextChanged?.Invoke(this, e);
UpdateVisibleScrollBars();
}
private bool busy = false;
private Size textSize = Size.Empty;
private void UpdateVisibleScrollBars()
{
if (busy) return;
busy = true;
bool c1 = false; // chars == Text.Length; // TODO: this not working for WordWrap = false
bool c2 = NumberOfPhysicalLinesInTextBox(this) > 2;
if (c1 && c2)
{
ScrollBars = ScrollBars.Both;
}
else if (c1)
{
ScrollBars = ScrollBars.Horizontal;
}
else if (c2)
{
ScrollBars = ScrollBars.Vertical;
}
else
{
ScrollBars = ScrollBars.None;
}
ScrollToCaret();
busy = false;
}
protected override void OnFontChanged(EventArgs e)
{
base.OnFontChanged(e);
UpdateVisibleScrollBars();
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
//UpdateVisibleScrollBars();
}
[Obsolete]
public event EventHandler ActualTextChanged;
}
Related
I am tampering with ToolStrip modifications as of now and is trying to make the submenu also transparent like the MenuStrip. I can't manage to make the submenu's property to be like the menu itself.
How do I do that?
Here's my code for the modifications:
public class ArrowRenderer : ToolStripProfessionalRenderer
{
public ArrowRenderer() : base(new LeftMenuColorTable())
{
}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
var tsMenuItem = e.Item as ToolStripMenuItem;
if (tsMenuItem != null)
e.TextColor = Color.White;
base.OnRenderItemText(e);
}
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
var tsMenuItem = e.Item as ToolStripMenuItem;
if (tsMenuItem != null)
e.ArrowColor = Color.White;
base.OnRenderArrow(e);
}
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
{
var tsMenuItem = e.Item as ToolStripMenuItem;
if (tsMenuItem != null)
e.Item.BackColor = Color.Black;
base.OnRenderMenuItemBackground(e);
}
}
public class LeftMenuColorTable : ProfessionalColorTable
{
public override Color MenuItemSelected
{
// when the menu is selected
get { return ColorTranslator.FromHtml("#494f52"); }
}
public override Color ToolStripBorder
{
get { return ColorTranslator.FromHtml("#FFFFFF"); }
}
public override Color ToolStripDropDownBackground
{
get { return Color.White; }
}
}
internal void SetTrayMenu()
{
if (m_menu != null)
if (notifyIcon.ContextMenuStrip != null)
notifyIcon.ContextMenuStrip.Refresh();
m_menu = new ContextMenuStrip();
m_menu.Renderer = new ArrowRenderer();
m_menu.AllowTransparency = true;
m_menu.Opacity = 0.8;
m_menu.BackColor = Color.Black;
}
Because the ToolStripDropDownMenu that hosts/lists the sub items or DropDownItems is not the same object that the ContextMenuStrip inherits. Hence you need to apply the same settings for each sub menu or DropDown.
The SetTrayMenu() should do:
internal void SetTrayMenu()
{
if (m_menu != null && notifyIcon.ContextMenuStrip != null)
//Why?
notifyIcon.ContextMenuStrip.Refresh();
else
{
m_menu = new ContextMenuStrip
{
Renderer = new ArrowRenderer(),
AllowTransparency = true,
Opacity = 0.8,
};
foreach (var dd in m_menu.Items.OfType<ToolStripMenuItem>()
.Where(x => x.HasDropDown))
{
var ddm = dd.DropDown as ToolStripDropDownMenu;
if (ddm != null)
{
ddm.AllowTransparency = true;
ddm.Opacity = 0.8;
}
}
m_menu.BackColor = Color.Black;
}
}
Before
After
Note: Opacity = 0.5 here.
I created a custom TimePicker and the renderers to android and iphone, with the objective to allow for that be nullable. As inspiration, was used the https://xamgirl.com/clearable-datepicker-in-xamarin-forms/
But, for some reason, the event is not firing when the time is set, thats happenen only in android, and more specific, back in android 8.1.
On shared project:
public class NullableTimePicker : TimePicker
{
public NullableTimePicker()
{
Time = DateTime.Now.TimeOfDay;
NullableTime = null;
Format = #"HH\:mm";
}
public string _originalFormat = null;
public static readonly BindableProperty PlaceHolderProperty =
BindableProperty.Create(nameof(PlaceHolder), typeof(string), typeof(NullableTimePicker), " : ");
public string PlaceHolder
{
get { return (string)GetValue(PlaceHolderProperty); }
set
{
SetValue(PlaceHolderProperty, value);
}
}
public static readonly BindableProperty NullableTimeProperty =
BindableProperty.Create(nameof(NullableTime), typeof(TimeSpan?), typeof(NullableTimePicker), null, defaultBindingMode: BindingMode.TwoWay);
public TimeSpan? NullableTime
{
get { return (TimeSpan?)GetValue(NullableTimeProperty); }
set { SetValue(NullableTimeProperty, value); UpdateTime(); }
}
private void UpdateTime()
{
if (NullableTime != null)
{
if (_originalFormat != null)
{
Format = _originalFormat;
}
}
else
{
Format = PlaceHolder;
}
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (BindingContext != null)
{
_originalFormat = Format;
UpdateTime();
}
}
protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == TimeProperty.PropertyName ||
(
propertyName == IsFocusedProperty.PropertyName &&
!IsFocused &&
(Time == DateTime.Now.TimeOfDay)))
{
AssignValue();
}
if (propertyName == NullableTimeProperty.PropertyName && NullableTime.HasValue)
{
Time = NullableTime.Value;
if (Time == DateTime.Now.TimeOfDay)
{
//this code was done because when date selected is the actual date the"DateProperty" does not raise
UpdateTime();
}
}
}
public void CleanTime()
{
NullableTime = null;
UpdateTime();
}
public void AssignValue()
{
NullableTime = Time;
UpdateTime();
}
}
On Android project:
public class NullableTimePickerRenderer : ViewRenderer<NullableTimePicker, EditText>
{
public NullableTimePickerRenderer(Context context) : base(context)
{
}
TimePickerDialog _dialog;
protected override void OnElementChanged(ElementChangedEventArgs<NullableTimePicker> e)
{
base.OnElementChanged(e);
this.SetNativeControl(new Android.Widget.EditText(Context));
if (Control == null || e.NewElement == null)
return;
this.Control.Click += OnPickerClick;
if (Element.NullableTime.HasValue)
Control.Text = DateTime.Today.Add(Element.Time).ToString(Element.Format);
else
this.Control.Text = Element.PlaceHolder;
this.Control.KeyListener = null;
this.Control.FocusChange += OnPickerFocusChange;
this.Control.Enabled = Element.IsEnabled;
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == Xamarin.Forms.TimePicker.TimeProperty.PropertyName ||
e.PropertyName == Xamarin.Forms.TimePicker.FormatProperty.PropertyName)
SetTime(Element.Time);
}
void OnPickerFocusChange(object sender, Android.Views.View.FocusChangeEventArgs e)
{
if (e.HasFocus)
{
ShowTimePicker();
}
}
protected override void Dispose(bool disposing)
{
if (Control != null)
{
this.Control.Click -= OnPickerClick;
this.Control.FocusChange -= OnPickerFocusChange;
if (_dialog != null)
{
_dialog.Hide();
_dialog.Dispose();
_dialog = null;
}
}
base.Dispose(disposing);
}
void OnPickerClick(object sender, EventArgs e)
{
ShowTimePicker();
}
void SetTime(TimeSpan time)
{
Control.Text = DateTime.Today.Add(time).ToString(Element.Format);
Element.Time = time;
}
private void ShowTimePicker()
{
CreateTimePickerDialog(this.Element.Time.Hours, this.Element.Time.Minutes);
_dialog.Show();
}
void CreateTimePickerDialog(int hours, int minutes)
{
NullableTimePicker view = Element;
_dialog = new TimePickerDialog(Context, (o, e) =>
{
view.Time = new TimeSpan(hours: e.HourOfDay, minutes: e.Minute, seconds: 0);
view.AssignValue();
((IElementController)view).SetValueFromRenderer(VisualElement.IsFocusedProperty, false);
Control.ClearFocus();
_dialog = null;
}, hours, minutes, true);
_dialog.SetButton("ok", (sender, e) =>
{
SetTime(Element.Time);
this.Element.Format = this.Element._originalFormat;
this.Element.AssignValue();
});
_dialog.SetButton2("clear", (sender, e) =>
{
this.Element.CleanTime();
Control.Text = this.Element.Format;
});
}
}
On iOS project:
public class NullableTimePickerRenderer : TimePickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
{
base.OnElementChanged(e);
var timePicker = (UIDatePicker)Control.InputView;
timePicker.Locale = new NSLocale("no_nb");
if (e.NewElement != null && this.Control != null)
{
this.UpdateDoneButton();
this.AddClearButton();
this.Control.BorderStyle = UITextBorderStyle.Line;
Control.Layer.BorderColor = UIColor.LightGray.CGColor;
Control.Layer.BorderWidth = 1;
if (Device.Idiom == TargetIdiom.Tablet)
{
this.Control.Font = UIFont.SystemFontOfSize(25);
}
}
}
private void UpdateDoneButton()
{
var toolbar = (UIToolbar)Control.InputAccessoryView;
var doneBtn = toolbar.Items[1];
doneBtn.Clicked += (sender, args) =>
{
NullableTimePicker baseTimePicker = this.Element as NullableTimePicker;
if (!baseTimePicker.NullableTime.HasValue)
{
baseTimePicker.AssignValue();
}
};
}
private void AddClearButton()
{
var originalToolbar = this.Control.InputAccessoryView as UIToolbar;
if (originalToolbar != null && originalToolbar.Items.Length <= 2)
{
var clearButton = new UIBarButtonItem("clear", UIBarButtonItemStyle.Plain, ((sender, ev) =>
{
NullableTimePicker baseTimePicker = this.Element as NullableTimePicker;
this.Element.Unfocus();
this.Element.Time = DateTime.Now.TimeOfDay;
baseTimePicker.CleanTime();
}));
var newItems = new List<UIBarButtonItem>();
foreach (var item in originalToolbar.Items)
{
newItems.Add(item);
}
newItems.Insert(0, clearButton);
originalToolbar.Items = newItems.ToArray();
originalToolbar.SetNeedsDisplay();
}
}
}
This code works fine on iOS and Android version 8.1 or higher, but in lower version, this just not fire the event to set time, setting always the default time.
I'm also provided a git repo with the code, maybe, make easily understand my problem.
https://github.com/aismaniotto/Nullable24hTimePicker
Thanks for your help. After more digging on code and debugging, I realize the problem was on OkButton implementation. On Android 8, apparently, the callback from the TimePickerDialog was called before the OkButton implementation e what was there, is ignored. On the older version, the OKButton implementation was called before and, for some reason, cancel the invocation of the callback.
That way, removing the OkButton implementation, the problem was solved... remembering that on the working version, was ignored anyway. Eventually, I will commit to the repository, to be registered.
Thanks a lot.
I have here the circle image, what I want to do is to put some color in specific position.. For example, when I click the button1, the left side of the circle will be filled by red, and when I click the button2, the right side will be filled by as well, and when I click the button1 again, the color will be removed, and so on...
I've done some research about it, and found out 2 ways to do it. First is, to layover the circle with another image. Second is to draw, and use the Graphics class in C#..
Now, my question is, is there another possible way to do it? What is the best way?
P.S: The purpose of this is for the tooth chart. :)
Here's a Resizable, Clickable, UserControl based on qing`s post. You can click on the regions directly to toggle them, or change them via code.
public partial class ToothChart : UserControl
{
public ToothChart()
{
InitializeComponent();
this.DoubleBuffered = true;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (this.ParentForm != null)
{
this.ParentForm.FormClosing += (s, evt) => { OnHandleDestroyed(new EventArgs()); };
}
}
protected override void OnHandleDestroyed(EventArgs e)
{
base.OnHandleDestroyed(e);
if (this._pathTop != null)
{
this._pathTop.Dispose();
this._pathTop = null;
}
if (this._pathRight != null)
{
this._pathRight.Dispose();
this._pathRight = null;
}
if (this._pathBottom != null)
{
this._pathBottom.Dispose();
this._pathBottom = null;
}
if (this._pathLeft != null)
{
this._pathLeft.Dispose();
this._pathLeft = null;
}
if (this._pathCenter != null)
{
this._pathCenter.Dispose();
this._pathCenter = null;
}
}
private GraphicsPath _pathTop = null;
private GraphicsPath _pathLeft = null;
private GraphicsPath _pathBottom = null;
private GraphicsPath _pathRight = null;
private GraphicsPath _pathCenter = null;
private bool _TopRegion = false;
public bool TopRegion
{
get
{
return _TopRegion;
}
set
{
if (_TopRegion != value)
{
_TopRegion = value;
this.Invalidate();
}
}
}
private bool _RightRegion = false;
public bool RightRegion
{
get
{
return _RightRegion;
}
set
{
if (_RightRegion != value)
{
_RightRegion = value;
this.Invalidate();
}
}
}
private bool _BottomRegion = false;
public bool BottomRegion
{
get
{
return _BottomRegion;
}
set
{
if (_BottomRegion != value)
{
_BottomRegion = value;
this.Invalidate();
}
}
}
private bool _LeftRegion = false;
public bool LeftRegion
{
get
{
return _LeftRegion;
}
set
{
if (_LeftRegion != value)
{
_LeftRegion = value;
this.Invalidate();
}
}
}
private bool _CenterRegion = false;
public bool CenterRegion
{
get
{
return _CenterRegion;
}
set
{
if (_CenterRegion != value)
{
_CenterRegion = value;
this.Invalidate();
}
}
}
protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);
if (this.IsHandleCreated && this._pathTop != null)
{
this.UpdateRegions();
}
}
private void UpdateRegions()
{
int diameterBig = Math.Min(this.Width, this.Height) - 10;
int diameterSmall = Math.Min(this.Width, this.Height) / 3;
if (diameterBig > 0 && diameterSmall > 0)
{
Point _centerPoint = new Point(this.Width / 2, this.Height / 2);
Rectangle rectangle = new Rectangle(_centerPoint.X - diameterBig / 2, _centerPoint.Y - diameterBig / 2, diameterBig, diameterBig);
Rectangle rectangle2 = new Rectangle(_centerPoint.X - diameterSmall / 2, _centerPoint.Y - diameterSmall / 2, diameterSmall, diameterSmall);
_pathTop.Reset();
_pathTop.AddArc(rectangle, 225, 90);
_pathTop.AddArc(rectangle2, -45, -90);
_pathLeft.Reset();
_pathLeft.AddArc(rectangle, 135, 90);
_pathLeft.AddArc(rectangle2, -135, -90);
_pathBottom.Reset();
_pathBottom.AddArc(rectangle, 45, 90);
_pathBottom.AddArc(rectangle2, -225, -90);
_pathRight.Reset();
_pathRight.AddArc(rectangle, -45, 90);
_pathRight.AddArc(rectangle2, -315, -90);
_pathCenter.Reset();
_pathCenter.AddEllipse(rectangle2);
this.Invalidate();
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (this.IsHandleCreated)
{
if (this._pathTop == null)
{
this._pathTop = new GraphicsPath();
this._pathRight = new GraphicsPath();
this._pathBottom = new GraphicsPath();
this._pathLeft = new GraphicsPath();
this._pathCenter = new GraphicsPath();
this.UpdateRegions();
}
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
if (this.TopRegion)
{
e.Graphics.FillPath(Brushes.Blue, _pathTop);
}
e.Graphics.DrawPath(Pens.Black, _pathTop);
if (this.RightRegion)
{
e.Graphics.FillPath(Brushes.DarkRed, _pathRight);
}
e.Graphics.DrawPath(Pens.Black, _pathRight);
if (this.BottomRegion)
{
e.Graphics.FillPath(Brushes.Teal, _pathBottom);
}
e.Graphics.DrawPath(Pens.Black, _pathBottom);
if (this.LeftRegion)
{
e.Graphics.FillPath(Brushes.Yellow, _pathLeft);
}
e.Graphics.DrawPath(Pens.Black, _pathLeft);
if (this.CenterRegion)
{
e.Graphics.FillPath(Brushes.LightGreen, _pathCenter);
}
e.Graphics.DrawPath(Pens.Black, _pathCenter);
}
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
Point p = new Point(e.X, e.Y);
if (this._pathTop.IsVisible(p))
{
this.TopRegion = !this.TopRegion;
}
else if (this._pathRight.IsVisible(p))
{
this.RightRegion = !this.RightRegion;
}
else if (this._pathBottom.IsVisible(p))
{
this.BottomRegion = !this.BottomRegion;
}
else if (this._pathLeft.IsVisible(p))
{
this.LeftRegion = !this.LeftRegion;
}
else if (this._pathCenter.IsVisible(p))
{
this.CenterRegion = !this.CenterRegion;
}
}
}
I want to create a TreeView column for a DataGridView. I have followed the example in here by extending the TreeView as below.
public class TreeViewEditingControl : TreeView, IDataGridViewEditingControl
public class TreeViewCell : DataGridViewComboBoxCell // Not sure whether this should be DataGridViewTextBoxCell
This is my issue. I can see the Treeview in cells, but I don't know how to increase the height of the Cell/TreeView when user click on a cell (as ComboBox expands). Does anyone have any idea on this?
I would spawn a new borderless form with a TreeCtrl Docked inside, I've done this with a CalendarControl and it works well. The user will not know the difference if you set the upper left hand corner of the form to the upper left hand corner of the cell that is being edited. Hope this is what you are looking for.
Edit:
Here is an implementation I did for a File Selection Cell. It has a Browse button that appears in the cell when you click it for editing and it opens a FileOpenDialog. The code is lengthy, but I think you can pick out the parts you need to implement.
public class DataGridViewFileColumn : DataGridViewColumn
{
public DataGridViewFileColumn() : base(new DataGridViewFileCell())
{
BrowseLabel = "...";
SaveFullPath = false;
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
// Ensure that the cell used for the template is a DataGridViewFileCell.
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(DataGridViewFileCell)))
{
throw new InvalidCastException("Must be a DataGridViewFileCell");
}
base.CellTemplate = value;
}
}
[Description("Label to place on Browse button"),Category("Appearance")]
[DefaultValue("...")]
public string BrowseLabel
{
get;
set;
}
[Description("Save full path name"), Category("Behavior")]
[DefaultValue(true)]
public bool SaveFullPath
{
get;
set;
}
}
public class DataGridViewFileCell : DataGridViewTextBoxCell
{
public DataGridViewFileCell() : base()
{
}
public override void InitializeEditingControl(int rowIndex, object
initialFormattedValue, DataGridViewCellStyle dataGridViewCellStyle)
{
// Set the value of the editing control to the current cell value.
base.InitializeEditingControl(rowIndex, initialFormattedValue,
dataGridViewCellStyle);
FileEditingControl ctl = (FileEditingControl)DataGridView.EditingControl;
// Use the default row value when Value property is null.
if (this.Value == null)
{
ctl.Filename = this.DefaultNewRowValue.ToString();
}
else
{
ctl.Filename = this.Value.ToString();
}
}
public override Type EditType
{
get
{
// Return the type of the editing control that DataGridViewFileCell uses.
return typeof(FileEditingControl);
}
}
public override Type ValueType
{
get
{
// Return the type of the value that DataGridViewFileCell contains.
return typeof(string);
}
}
}
class FileEditingControl : FileTextBox, IDataGridViewEditingControl
{
DataGridView dataGridView;
private bool valueChanged = false;
int rowIndex;
public FileEditingControl()
{
}
#region IDataGridViewEditingControl implementations
public object EditingControlFormattedValue
{
get
{
return Filename;
}
set
{
if (value is String)
{
try
{
Filename = (String)value;
}
catch
{
Filename = value.ToString();
}
}
}
}
public object GetEditingControlFormattedValue(
DataGridViewDataErrorContexts context)
{
return EditingControlFormattedValue;
}
public void ApplyCellStyleToEditingControl(
DataGridViewCellStyle dataGridViewCellStyle)
{
this.Font = dataGridViewCellStyle.Font;
}
public int EditingControlRowIndex
{
get
{
return rowIndex;
}
set
{
rowIndex = value;
}
}
public bool EditingControlWantsInputKey(
Keys key, bool dataGridViewWantsInputKey)
{
switch (key & Keys.KeyCode)
{
case Keys.Left:
case Keys.Up:
case Keys.Down:
case Keys.Right:
case Keys.Home:
case Keys.End:
case Keys.PageDown:
case Keys.PageUp:
return true;
default:
return !dataGridViewWantsInputKey;
}
}
public void PrepareEditingControlForEdit(bool selectAll)
{
}
public bool RepositionEditingControlOnValueChange
{
get
{
return false;
}
}
public DataGridView EditingControlDataGridView
{
get
{
return dataGridView;
}
set
{
dataGridView = value;
}
}
public bool EditingControlValueChanged
{
get
{
return valueChanged;
}
set
{
valueChanged = value;
}
}
public Cursor EditingPanelCursor
{
get
{
return base.Cursor;
}
}
#endregion
protected override void OnValueChanged(FileEventArgs eventargs)
{
// Notify the DataGridView that the contents of the cell
// have changed.
valueChanged = true;
this.EditingControlDataGridView.NotifyCurrentCellDirty(true);
base.OnValueChanged(eventargs);
}
}
public partial class FileTextBox : UserControl
{
#region Constructors
public FileTextBox()
{
InitializeComponent();
Tooltip = new ToolTip();
SaveFullPath = false;
AllowMultipleFiles = false;
BrowseLabel = "...";
}
#endregion Constructors
#region Properties
/// <summary>
/// Tooltip object used to show full path name
/// </summary>
private ToolTip Tooltip;
/// <summary>
/// Return the full path or just the filename?
/// </summary>
[Description("Save Full Path"), Category("Behavior")]
[DefaultValue(false)]
public bool SaveFullPath
{
get;
set;
}
/// <summary>
/// String representing the filename for this control
/// </summary>
public override string Text
{
get
{
return base.Text;
}
set
{
if (base.Text != value)
{
base.Text = value;
Tooltip.SetToolTip(this, base.Text);
Invalidate();
OnValueChanged(new FileEventArgs(base.Text));
}
}
}
[Description("Browse Label"), Category("Appearance")]
[DefaultValue("...")]
public string BrowseLabel
{
get
{
return Browse.Text;
}
set
{
Browse.Text = value;
Browse.Width = TextRenderer.MeasureText(Browse.Text, Browse.Font).Width + 8;
Browse.Location = new Point(this.Width - Browse.Width, Browse.Location.Y);
}
}
[Description("Allow Multiple Files"), Category("Behavior")]
[DefaultValue(false)]
public bool AllowMultipleFiles
{
get;
set;
}
/// <summary>
/// Selected filename (same as Text property)
/// </summary>
[Description("Filename"), Category("Data")]
public string Filename
{
get { return Text; }
set { Text = value; }
}
#endregion Properties
#region Event Handlers
/// <summary>
/// Event raised when
/// </summary>
public event EventHandler ValueChanged;
protected virtual void OnValueChanged(FileEventArgs eventargs)
{
eventargs.Filename = Filename;
if (this.ValueChanged != null)
this.ValueChanged(this, eventargs);
}
private void Browse_Click(object sender, EventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.FileName = Text;
dlg.Multiselect = AllowMultipleFiles;
if (dlg.ShowDialog() == DialogResult.OK)
{
if (SaveFullPath)
Text = dlg.FileName;
else
Text = dlg.SafeFileName;
}
}
protected override void OnPaint(PaintEventArgs e)
{
// Draw the client window
Rectangle r = new Rectangle(new Point(0, 0), new Size(Size.Width-1, Size.Height-1));
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(SystemColors.Window), r);
g.DrawRectangle(new Pen(VisualStyleInformation.TextControlBorder), r);
r.Y += Margin.Top;
r.Width -= Browse.Width;
// Fill with Text
TextRenderer.DrawText(g, Text, Font, r, ForeColor, TextFormatFlags.PathEllipsis);
base.OnPaint(e);
}
private void FileTextBox_DragDrop(object sender, DragEventArgs e)
{
DataObject data = (DataObject)e.Data;
StringCollection filenames = data.GetFileDropList();
if ( filenames.Count == 1)
Text = filenames[0];
}
private void FileTextBox_DragEnter(object sender, DragEventArgs e)
{
DataObject data = (DataObject)e.Data;
StringCollection filenames = data.GetFileDropList();
if (/*!AllowMultipleFiles &&*/ filenames.Count == 1)
e.Effect = DragDropEffects.Link;
}
#endregion Event Handlers
}
public class FileEventArgs : EventArgs
{
public FileEventArgs(string Text)
{
Filename = Text;
}
/// <summary>
/// Name of the file in the control
/// </summary>
public String Filename { get; set; }
}
I need to implement own TreeView with blinked TreeNode. My prototype is:
public class BlinkTreeView : TreeView
{
private int blinkInterval;
private bool blinkState;
[Category("Behavior"), Browsable(true)]
public Icon BlinkIcon { get; set; }
[Category("Behavior"), Browsable(true)]
public Icon SelectedBlinkIcon { get; set; }
[Category("Behavior"), Browsable(true), DefaultValue(1000)]
public int BlinkInterval {
get
{
return blinkInterval;
}
set
{
blinkInterval = value;
if (value > 0)
{
blinkTimer.Interval = value;
blinkTimer.Start();
}
else
{
blinkTimer.Stop();
blinkState = false;
Invalidate();
}
}
}
private Timer blinkTimer;
public BlinkTreeView()
: base()
{
blinkTimer = new Timer();
blinkTimer.Tick += new EventHandler(blinkTimer_Tick);
blinkState = false;
this.DrawMode = TreeViewDrawMode.OwnerDrawAll;
}
void blinkTimer_Tick(object sender, EventArgs e)
{
if (BlinkInterval > 0)
{
blinkState = !blinkState;
}
else
{
blinkState = false;
}
Invalidate();
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e)
{
e.DrawDefault = true;
base.OnDrawNode(e);
if (blinkState)
{
//here i want to draw blinked item, but i can't redraw item icons and text.
}
}
}
In OnDrawNode i can't redraw icon and text of node.
Any idea how to solve this?
Just a thought, but you could invert (xor) over the item without making the tree into an owner-draw control. I think it works something like the following:
using (Graphics g = Graphics.FromHwnd(Tree.Handle))
{
TreeNode node = myBlinkyNode;
if (node != null)
{
using(Region myRegion = new Region(node.Bounds))
myRegion.Xor(xorRect);
}
}
You'll need to keep track if the blink is visible or not and handle the Paint event so that you can re-draw the inverted rectangle.
Have a timer toggle the state of the blinking nodes, i.e.:
Node.ForeColor = Node.ForeColor == Color.White ? Color.Black : Color.White;