What is the easiest way to have Visual Studio-like editor for string in PropertyGrid? For example in Autos/Locals/Watches you can preview/edit string values in-line but you can also click on magnifying glass and see string in external window.
You can do this via a UITypeEditor, as below. Here I'm using it on an individual property, but IIRC you can also subvert all strings (so that you don't need to decorate all the properties):
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using(var frm = new Form { Controls = { new PropertyGrid {
Dock = DockStyle.Fill, SelectedObject = new Foo { Bar = "abc"}}}})
{
Application.Run(frm);
}
}
}
class Foo
{
[Editor(typeof(FancyStringEditor), typeof(UITypeEditor))]
public string Bar { get; set; }
}
class FancyStringEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
var svc = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
if (svc != null)
{
using (var frm = new Form { Text = "Your editor here"})
using (var txt = new TextBox { Text = (string)value, Dock = DockStyle.Fill, Multiline = true })
using (var ok = new Button { Text = "OK", Dock = DockStyle.Bottom })
{
frm.Controls.Add(txt);
frm.Controls.Add(ok);
frm.AcceptButton = ok;
ok.DialogResult = DialogResult.OK;
if (svc.ShowDialog(frm) == DialogResult.OK)
{
value = txt.Text;
}
}
}
return value;
}
}
To apply this for all string members: instead of adding the [Editor(...)], apply the following somewhere early in the app:
TypeDescriptor.AddAttributes(typeof(string), new EditorAttribute(
typeof(FancyStringEditor), typeof(UITypeEditor)));
Related
In Visual Studio 2022, in designer, on a form, I have many different types of controls like buttons, labels and others.
How do I select all buttons, for instance?
What I am doing now is pressing Shift key and then selecting the buttons one by one, which is taking to much time.
You can create a new Visual Studio extension which adds a new menu, or a new toolbar, or a new button to an existing window. Then using IDesignerHost and INestedContainer, get the selectable components, and filter them to get list of buttons, then using ISelectionService select them.
You can find all the building blocks of the solution here and there, for example in my following posts:
Get all controls of the current form at design-time
Add a button to solution explorer to open .cs files in code editor
But to keep it simple to use and simple for test, I'll use a different solution that I used here, which is adding a new designer verb to context menu, using a base class.
To do so, Create a BaseForm class deriving from Form. Then each form which drives from this class will have above functionality that you see in the animation. Here is the code of the base form:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;
public partial class BaseForm : Form
{
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (DesignMode && Site != null)
{
var host = Site.GetService(typeof(IDesignerHost)) as IDesignerHost;
if (host != null)
{
var designer = (DocumentDesigner)host.GetDesigner(this);
if (designer != null)
{
designer.ActionLists.Clear();
designer.ActionLists.Add(
new MyActionList(host, designer));
}
}
}
}
public class MyActionList : DesignerActionList
{
IDesignerHost host;
ControlDesigner designer;
public MyActionList(IDesignerHost host,
ControlDesigner designer) : base(designer.Component)
{
this.host = host;
this.designer = designer;
}
private void SelectAllButtons()
{
var buttons = GetSelectableComponents(host).OfType<Button>().ToList();
var svc = host.GetService(typeof(ISelectionService)) as ISelectionService;
if (svc != null)
{
svc.SetSelectedComponents(buttons);
}
}
public override DesignerActionItemCollection GetSortedActionItems()
{
var items = new DesignerActionItemCollection();
var category = "New Actions";
items.Add(new DesignerActionMethodItem(this, "SelectAllButtons",
"Select all buttons", category, true));
return items;
}
private List<IComponent> GetSelectableComponents(IDesignerHost host)
{
var components = host.Container.Components;
var list = new List<IComponent>();
foreach (IComponent c in components)
list.Add(c);
for (var i = 0; i < list.Count; ++i)
{
var component1 = list[i];
if (component1.Site != null)
{
var service = (INestedContainer)component1.Site.GetService(
typeof(INestedContainer));
if (service != null && service.Components.Count > 0)
{
foreach (IComponent component2 in service.Components)
{
if (!list.Contains(component2))
list.Add(component2);
}
}
}
}
return list;
}
}
}
I am using element host to use WPF spell checker textbox in my winforms.
I want to override the context menu that appears on misspelled red squiggles to mouse hover instead of right click.
How to do that?
Tried overriding the behavior but it is still the same:
using System;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms.Integration;
using System.Windows.Forms.Design;
[Designer(typeof(ControlDesigner))]
class SpellCheck: ElementHost
{
privated TextBox box;
public SpellCheck()
{
box = new TextBox();
base.Child = box;
box.TextChanged += (s, e) => OnTextChanged(EventArgs.Empty);
box.SpellCheck.IsEnabled = true;
box.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
}
[DefaultValue(false)]
public bool Multiline
{
//checks for multiline
}
public bool IsEnabled
{
//checks for spell check enabled or not
}
[DefaultValue(false)]
public bool WordWrap
{
//does wordwraps
}
[DefaultValue(false)]
public int MaxLength
{
//maxlength property
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new System.Windows.UIElement Child
{
get { return base.Child; }
set { }
}
}
It shows spell suggestions on right click. I want to change it to Mouse hover or any other mouse events.
You could use TextBox.PreviewMouseMove to show a popup to achieve this.
You also need to create a new Form to serve as a popup. This Form could contain a FlowLayoutPanel to host the suggestions. In this example the Form is named CustomPopup:
box.PreviewMouseMove += OpenContextMenuOnMouseMove;
The event handler for TextBox.PreviewMouseMove:
private void OpenContextMenuOnMouseMove(object sender, System.Windows.Input.MouseEventArgs mouseEventArgs)
{
var textBox = sender as System.Windows.Controls.TextBox;
var mouseOverTextIndex = textBox.GetCharacterIndexFromPoint(mouseEventArgs.GetPosition(textBox), false);
// Pointer is not over text
if (mouseOverTextIndex.Equals(-1))
{
return;
}
int spellingErrorIndex = textBox.GetNextSpellingErrorCharacterIndex(mouseOverTextIndex, LogicalDirection.Forward);
// No spelling errors
if (spellingErrorIndex.Equals(-1))
{
return;
}
int startOfWordIndex = mouseOverTextIndex;
while (startOfWordIndex != -1 && !textBox.Text[startOfWordIndex].Equals(' '))
{
startOfWordIndex--;
}
var endOfWordIndex = textBox.Text.IndexOf(" ", mouseOverTextIndex, StringComparison.OrdinalIgnoreCase);
if (endOfWordIndex.Equals(-1))
{
endOfWordIndex = textBox.Text.Length - 1;
}
// Spelling error doesn't belong to current mouse over word
if (spellingErrorIndex < startOfWordIndex || spellingErrorIndex > endOfWordIndex)
{
return;
}
using(CustomPopup popup = new CustomPopup())
{
// Create clickable suggestions for the CustomPopup.
foreach (var suggestion in textBox.GetSpellingError(spellingErrorIndex).Suggestions)
{
// Each Button represents a suggestion.
var suggestionButton = new System.Windows.Forms.Button() { Text = suggestion };
var fixSpellingErrorEventArgs = new FixSpellingErrorEventArgs()
{
TargetTextBox = textBox,
WordStartIndex = startOfWordIndex,
WordEndIndex = endOfWordIndex,
Suggestion = suggestion
};
// The Button.Click callback will apply the selected fix
suggestionButton.Click += (s, e) => FixSpellingError_OnButtonClicked(fixSpellingErrorEventArgs);
// TODO::Replace the line with a public member of CustomPopup Form: CustomPopup.AddPanelContent(Control):void.
// e.g. public void AddPanelContent(Control control) { this.FlowLayoutPanel1.Controls.Add(suggestionButton); }
// and use it like: popup.AddPanelContent(suggestionButton);
popup.FlowLayoutPanel1.Controls.Add(suggestionButton);
}
popup.SetDesktopLocation((int) mouseEventArgs.GetPosition(textBox).X, (int) mouseEventArgs.GetPosition(textBox).Y);
popup.ShowDialog(this);
}
}
// The event handler that applies the selected fix.
// Invoked on popup button clicked.
private void FixSpellingError_OnButtonClicked(FixSpellingErrorEventArgs e)
{
// Select misspelled word and replace it with the selected fix
e.TargetTextBox.SelectionStart = e.WordStartIndex;
e.TargetTextBox.SelectionLength = e.WordEndIndex - e.WordStartIndex;
e.TargetTextBox.SelectedText = e.Suggestion;
}
The custom event arg object for Button.Click event
class FixSpellingErrorEventArgs : EventArgs
{
public System.Windows.Controls.TextBox TargetTextBox { get; set; }
public int WordStartIndex { get; set; }
public int WordEndIndex { get; set; }
public string Suggestion { get; set; }
}
To enhance the example create the logic how or when the popup will disappear (time out and focus lost?).
When you bind a control's property using DataBindings to a datasource, the property grid will show a little purple or black symbol in that property coresponding grid item.
Even when you place your PropertyGrid on the form and set its SelectedObject property to the control with the bound property, that PropertyGrid on the form will show that symbol as well.
But only at design time.
Is there a (simple) way to make the very same PropertyGrid to show this symbol at runtime?
It is handled by Visual Studio designer internal things. But you also can add this feature to PropertyGrid:
You need to implement IPropertyValueUIService and using reflection, assign an instance of the service to the grid entries which should show the glyph. This implementation has a method GetPropertyUIValueItems which can be used to provide that glyph and a tooltip to show near the property label in PropertyGrid. Those values will be used in PaintLabel method of the property grid entry.
Then create an inherited PropertyGrid and override OnSelectedObjectsChanged and OnPropertySortChanged. In those method for each property grid entry item which is presenting a property which is in data bindings collection, set an instance of the implemented IPropertyValueUIService as value of pvSvc private property of the PropertyGrid and attach an event handler which will be called when PropertyGrid requests for additional information about the property. By attaching an event handler using GetPropertyUIValueItems to you can return the tooltip and image which you are going to show in front of the property.
Example
You can download the full example here:
r-aghaei/PropertyGridDataBindingGlyph
You can find main classes of the implementation as follows.
PropertyValueUIService
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing.Design;
namespace PropertyGridDataBindingGlyph
{
public class PropertyValueUIService : IPropertyValueUIService
{
private PropertyValueUIHandler handler;
private ArrayList items;
public event EventHandler PropertyUIValueItemsChanged;
public void NotifyPropertyValueUIItemsChanged()
{
PropertyUIValueItemsChanged?.Invoke(this, EventArgs.Empty);
}
public void AddPropertyValueUIHandler(PropertyValueUIHandler newHandler)
{
handler += newHandler ?? throw new ArgumentNullException("newHandler");
}
public PropertyValueUIItem[] GetPropertyUIValueItems(ITypeDescriptorContext context, PropertyDescriptor propDesc)
{
if (propDesc == null)
throw new ArgumentNullException("propDesc");
if (this.handler == null)
return new PropertyValueUIItem[0];
lock (this)
{
if (this.items == null)
this.items = new ArrayList();
this.handler(context, propDesc, this.items);
int count = this.items.Count;
if (count > 0)
{
PropertyValueUIItem[] propertyValueUiItemArray = new PropertyValueUIItem[count];
this.items.CopyTo((Array)propertyValueUiItemArray, 0);
this.items.Clear();
return propertyValueUiItemArray;
}
}
return null;
}
public void RemovePropertyValueUIHandler(PropertyValueUIHandler newHandler)
{
handler -= newHandler ?? throw new ArgumentNullException("newHandler");
}
}
}
ExPropertyGrid
using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;
namespace PropertyGridDataBindingGlyph
{
public class ExPropertyGrid : PropertyGrid
{
Bitmap dataBitmap;
public ExPropertyGrid()
{
dataBitmap = new Bitmap(typeof(ControlDesigner).Assembly
.GetManifestResourceStream("System.Windows.Forms.Design.BoundProperty.bmp"));
dataBitmap.MakeTransparent();
}
protected override void OnSelectedObjectsChanged(EventArgs e)
{
base.OnSelectedObjectsChanged(e);
this.BeginInvoke(new Action(() => { ShowGlyph(); }));
}
protected override void OnPropertySortChanged(EventArgs e)
{
base.OnPropertySortChanged(e);
this.BeginInvoke(new Action(() => { ShowGlyph(); }));
}
private void ShowGlyph()
{
var grid = this.Controls[2];
var field = grid.GetType().GetField("allGridEntries",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance | BindingFlags.FlattenHierarchy);
var value = field.GetValue(grid);
if (value == null)
return;
var entries = (value as IEnumerable).Cast<GridItem>().ToList();
if (this.SelectedObject is Control)
{
((Control)this.SelectedObject).DataBindings.Cast<Binding>()
.ToList().ForEach(binding =>
{
var item = entries.Where(x => x.PropertyDescriptor?.Name == binding.PropertyName).FirstOrDefault();
var pvSvcField = item.GetType().GetField("pvSvc", BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.FlattenHierarchy);
IPropertyValueUIService pvSvc = new PropertyValueUIService();
pvSvc.AddPropertyValueUIHandler((context, propDesc, valueUIItemList) =>
{
valueUIItemList.Add(new PropertyValueUIItem(dataBitmap, (ctx, desc, invokedItem) => { }, GetToolTip(binding)));
});
pvSvcField.SetValue(item, pvSvc);
});
}
}
private static string GetToolTip(Binding binding)
{
var value = "";
if (binding.DataSource is ITypedList)
value = ((ITypedList)binding.DataSource).GetListName(new PropertyDescriptor[] { });
else if (binding.DataSource is Control)
value = ((Control)binding.DataSource).Name;
else if (binding.DataSource is Component)
value = ((Component)binding.DataSource).Site?.Name;
if (string.IsNullOrEmpty(value))
value = "(List)";
return value + " - " + binding.BindingMemberInfo.BindingMember;
}
}
}
Now I have a WindowsForm with a textbox and a calendar, and two buttons (continue and finish).
The user has to enter a Text to the box and select a date.
If he presses continue, the text and the date have to be written to a json file.
Then, the same window should open again and he can enter new values.
Finally, if he`s clicking the finish button, the Values should be written to the file and the window can close.
(User should click finish already after he entered the first value).
I already created a get/set class:
class Nachrichten_Felder
{
public string Nachrichten { get; set; }
public string Datum { get; set; }
}
How can I achieve this job? I really don`t know how to write the text and date to the json and reopen the window again...
For C# I use JSON.NET.
Ok, I made homework for you.
using Newtonsoft.Json;
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
public Form1()
{
//InitializeComponent();
var newsButton = new Button { Parent = this, Text = "Show" };
newsButton.Click += NewsButton_Click;
}
private void NewsButton_Click(object sender, EventArgs e)
{
DialogResult result;
do
{
using (var newsForm = new NewsForm())
result = newsForm.ShowDialog();
} while (result == DialogResult.OK);
}
}
class NewsForm : Form
{
TextBox newsTextBox;
MonthCalendar monthCalendar;
Button continueButton;
Button finishButton;
Nachrichten_Felder news;
public NewsForm()
{
Width = 400;
newsTextBox = new TextBox { Parent = this, Multiline = true, Size = new Size(200, 200) };
monthCalendar = new MonthCalendar { Parent = this, Location = new Point(220, 0), MaxSelectionCount = 1 };
continueButton = new Button { Parent = this, Text = "Continue", Location = new Point(200, 220), DialogResult = DialogResult.OK };
finishButton = new Button { Parent = this, Text = "Finish", Location = new Point(300, 220), DialogResult = DialogResult.Cancel };
news = new Nachrichten_Felder();
newsTextBox.DataBindings.Add("Text", news, "Nachrichten");
monthCalendar.DataBindings.Add("SelectionStart", news, "Datum");
this.FormClosing += NewsForm_FormClosing;
}
private void NewsForm_FormClosing(object sender, FormClosingEventArgs e)
{
string json = JsonConvert.SerializeObject(news, Formatting.Indented);
File.AppendAllText(#"news.txt", json);
}
}
class Nachrichten_Felder
{
public string Nachrichten { get; set; }
public DateTime Datum { get; set; }
}
}
I have created a new User Control that has a property like...
private Font m_DisplayFont;
public Font DisplayFont
{
get { return m_DisplayFont; }
set { m_DisplayFont = value; }
}
I want to set m_DisplayFont to the parent's font when I drop the new User Control into a container (Form, GroupBox, etc).
I currently have tried the following but can not get the parent when the class is constructed. Any suggested would be welcome. Thanks!
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;
namespace MyTestControl
{
public partial class UserControl1 : ProgressBar
{
private Font m_DisplayFont;
public Font DisplayFont
{
get { return m_DisplayFont; }
set { m_DisplayFont = value; }
}
public UserControl1()
{
InitializeComponent();
object parent = base.Parent;
m_DisplayFont = null;
if (parent != null)
{
//See if parent contains a font
Type type = parent.GetType();
IList<PropertyInfo> props = new List<PropertyInfo>(type.GetProperties());
foreach (PropertyInfo propinfo in props)
{
if (propinfo.Name == "Font")
{
m_DisplayFont = (Font)propinfo.GetValue(parent, null);
}
}
}
if (m_DisplayFont == null) m_DisplayFont = new Font("Verdana", 20.25f);
}
}
}
You can use the ParentChanged event:
Occurs when the Parent property value changes.
private void ParentChanged(Object sender, EventArgs args)
{
var parent = this.Parent;
if (parent == null)
return;
var fontProp = parent
.GetType()
.GetProperty("Font");
var font = (fontProp == null) ?
new Font("Verdana", 20.25f) : (Font)fontProp.GetValue(parent, null);
this.m_DisplayFont = font;
}