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;
}
}
}
Related
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;
}
}
}
Here is the code that I have. I would like to know how I can detect when a user clicks a tab that is already selected as I want to toggle the icon for the aPage between play.png and pause.png plus I also want to call a method on APage.
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var aPage = new NavigationPage(new APage())
{
Title = "Play",
Icon = "play.png"
};
var bPage = new NavigationPage(new BPage())
{
Title = "Settings",
Icon = "b.png"
};
Children.Add(aPage);
Children.Add(bPage);
}
}
Note that if possible I would like to find a solution that does not involve custom renderers for both iOS and Android. I'm wondering can I redefine the TabbedPage and put the logic in that class?
I know you want to avoid using custom renderers, but this is only possible by using a Custom Renderer.
Code
Xamarin.Android Custom Renderer
using Android.Content;
using Android.Support.Design.Widget;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))]
namespace YourNameSpace
{
public class MainPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
MainPage _page;
public MainPageRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
_page = e.NewElement as MainPage;
else
_page = e.OldElement as MainPage;
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
System.Diagnostics.Debug.WriteLine("Tab Reselected");
//Handle Tab Reselected
}
}
}
Xamarin.iOS Custom Renderer
using System;
using System.Diagnostics;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(MainPage), typeof(MainPageRenderer))]
namespace YourNameSpace
{
public class MainPageRenderer : TabbedRenderer
{
MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
_page = e.NewElement as MainPage;
else
_page = e.OldElement as MainPage;
try
{
if (ViewController is UITabBarController tabBarController)
tabBarController.ViewControllerSelected += OnTabbarControllerItemSelected;
}
catch (Exception exception)
{
Debug.WriteLine(exception);
}
}
void OnTabbarControllerItemSelected(object sender, UITabBarSelectionEventArgs eventArgs)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
Debug.WriteLine("Tab Tapped");
//Handle Tab Tapped
}
}
}
}
Code credit: #Kyle https://stackoverflow.com/a/42909203/5953643
If you want to get selected tab then you need to use ItemSource and SelectedItem property like ListView.
You can do this easily in iOS, but in Android you need a custom renderer. Just check this blog
http://motzcod.es/post/162985782667/dynamically-changing-xamarin-forms-tab-icons-when-select
You can't. TabbedPage interited from MultiPage that you can check the source from here. All select, deselect, update, template and logic is implemented here. You suppose to watch CurrentPage property but it has value check if already selected, so you cannot use.
this.PropertyChanging += async (object sender, PropertyChangingEventArgs e) =>
{
if (e.PropertyName == "CurrentPage")
{
if (this.CurrentPage == null)
return;
}
};
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;
}
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)));
Is there any mechanism where I can get update notification when users try to open an menu item, like in MFC.
I know there is no direct way, but there should be lot of hacks, that's what I am asking.
What architecture?
In winforms (MenuStrip) you can use the DropDownOpening event - that do?
On the older MenuItem, there is the Popup event that works similarly.
I'm not sure about WPF...
This demonstrates both (MenuItem first, then ToolStripMenuItem):
using System;
using System.Windows.Forms;
static class Program {
[STAThread]
static void Main() {
// older menuitem
MenuItem mi;
using (Form form = new Form {
Menu = new MainMenu {
MenuItems = {
(mi = new MenuItem("abc"))
}
}
})
{
mi.MenuItems.Add("dummy");
mi.Popup += delegate {
mi.MenuItems.Clear();
mi.MenuItems.Add(DateTime.Now.ToLongTimeString());
};
Application.Run(form);
}
MenuStrip ms;
ToolStripMenuItem tsmi;
using (Form form = new Form {
MainMenuStrip = (ms = new MenuStrip {
Items = {
(tsmi = new ToolStripMenuItem("def"))
}
})
})
{
form.Controls.Add(ms);
tsmi.DropDownItems.Add("dummy");
tsmi.DropDownOpening += delegate {
tsmi.DropDownItems.Clear();
tsmi.DropDownItems.Add(DateTime.Now.ToLongTimeString());
};
Application.Run(form);
}
}
}