WinForm PropertyGrid change text in Help area - winforms

I want to change the Text in the Help area in WinForm PropertyGrid after the PropertyGrid is load. Here is my 2 attempts using reflection, but they both not working correctly.
Solution 1: I inherited the PropertyGrid and retrieve the doccomment, which is the control for the Help area. I have a separate button that calls the ChangeHelpText method to change the text property of doccomment. After it, I then call the Refresh method of the PropertyGrid. However, nothing changes. Also I assign HelpBackColor of the PropertyGrid, nothing changes as well. Any idea?
public void ChangeHelpText(String desc)
{
FieldInfo fi = this.GetType().BaseType.GetField("doccomment", BindingFlags.NonPublic | BindingFlags.Instance);
Control dc = fi.GetValue(this) as Control;
dc.Text = desc;
dc.BackColor = Color.AliceBlue;
fi.SetValue(this, dc);
}
Solution 2: The PropertyGrid's Help text reflects the DescriptionAttribute of the properties in the binding class. Therefor I use TypeDescriptor.GetProperties to retrieve all properties of the SelectedObject of the PropertyGrid, loop through them and retrieve the DescriptionAttribute, and change the description private field of the DescriptionAttribute to my text using reflection. Interestingly, if I put a break point where I reassign the DescriptionAttribute, this solution works partially as only some properties' DescriptionAttribute are changed and reflected in the PropertyGrid and others are not changed. If I don't put the breakpoint, nothing is changed. Everything is running in the STAThread.

The first solution doesn't work because you are setting the control's Text property which is not used for displaying the help text. The DocComment control has two label child controls which is used for displaying the help title (property label) and help text (property description attribute value). If you want to change the help text you have manipulate these two labels.
It is simpler to just call the method which updates these two controls. The sample code given below works but uses reflection to invoke the method.
public class CustomPropertyGrid : PropertyGrid
{
Control docComment = null;
Type docCommentType = null;
public void SetHelpText(string title, string helpText)
{
if (docComment == null)
{
foreach (Control control in this.Controls)
{
Type controlType = control.GetType();
if (controlType.Name == "DocComment")
{
docComment = control;
docCommentType = controlType;
}
}
}
BindingFlags aFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
MethodInfo aInfo = docCommentType.GetMethod("SetComment", aFlags);
if (aInfo != null)
{
aInfo.Invoke(docComment, new object[] { title, helpText });
}
}
}
To change the back color and fore color use the properties provided by the PropertyGrid.
propertyGrid1.HelpBackColor = Color.BlueViolet;
propertyGrid1.HelpForeColor = Color.Yellow;

Related

Initializing properties in custom control and making events

I created custom control PlaceHolderTextBox with some properties.
Here is my code:
class PlaceHolderTextBox:TextBox
{
public string PlaceHolderText { get; set; }
public Color PlaceHolderColor { get; set; }
public Font PlaceHolderFont { get; set; }
public Color StandardColor { get; set; }
public PlaceHolderTextBox()
{
GotFocus += OnGetFocus;
LostFocus += OnLostFocus;
TextChanged += OnTextChanged;
Text = PlaceHolderText;
ForeColor = PlaceHolderColor;
Font = PlaceHolderFont;
}
private void OnGetFocus(object sender,EventArgs e)
{
if (this.Text == this.PlaceHolderText)
{
ForeColor = StandardColor;
Text = "";
}
}
private void OnLostFocus(object sender, EventArgs e)
{
if (this.Text == "")
{
ForeColor = PlaceHolderColor;
Text = PlaceHolderText;
}
}
}
In designer, i set values:
And when i start program, i get empty textbox.
I think reason of that behaviour is that on time constructor executes, the properties are empty, but im not sure.
Also, i want to make events when i change these custom properties.
Is that possible?
Let's first focus on the problem which you are facing. But make sure that you read the Note.
Here is the problem → In constructor, you have set text to the value of PlaceHolderText property. At that time, PlaceHolderText is empty.
Even if you set a default hard-coded value for Text property in constructor, when you drop an instance of your custom textbox on the form, the InitializeNewComponent method of the TextBoxDesigner will set the Text property to empty string. If you close and reopen the designer, your text will appear.
Note - Why you should not show a placeholder text by setting Text property
It's definitely not a good idea to implement placeholder feature by setting and resetting Text property in GotFocus/LostFocus or Enter/Leave events, because:
It will have problems when using data-binding, it will raise validation errors when binding to a number or date property or properties which should be in a specific format.
When data-binding, if you press Save button, placeholder values will be saved in database unwantedly.
Based on your code, if user types the same value as you set for placeholder, then when losing focus, you are resetting it to empty. It's wrong.
To have a placeholder(also known as hint, watermark and cue-banner) you can use one of the following solutions: the native text box feature or a custom paint solution.

Setting observable object to NULL == CRASH

I have a List bound to a (Telerik) GridView. The selected item is a separate variable of type T which is assigned the object of the selected row in the GridView when the user clicks on a row. T is derived from ObservableObject. This means I am using MVVM Light Toolkit.
I need to deselect the row from my ViewModel in certain situations. On the GridView control this works, if the selected item is set to NULL in the ViewModel. Whenever I do this, MVVM reports a crash (NPE). I debugged it and saw that it is failing in ObservableObject.cs. It calls a method
protected bool Set<T>(
Expression<Func<T>> propertyExpression,
ref T field,
T newValue)
and crashes one line before return when calling RaisePropertyChanged(propertyExpression)
I don't know if this is working as designed or not. My problem is, that I need to set the selected Object to NULL in the ViewModel to deselect a row of my GridView in the View. I CANNOT use CodeBehind for the deselection!
Code I have:
public ObservableCollection<ContractTypeDto> ContractTypes { get; private set; }
public ContractTypeDto SelectedContractType
{
get { return _selectedContractType; }
set
{
Set(() => SelectedContractType, ref _selectedContractType, value);
RaisePropertyChanged(() => SelectedContractType);
}
}
When you click on a row in the grid it opens a new UserControl containing lots of details of this record. This control has its own ViewModel. I store the calling view Model (where the selected item is stored). When the page (control) is closed (destroyed) I have to deselect the row in the grid. I call a method like so:
protected void DeselectCallersSelectedItem()
{
if (CallingObject == typeof(ContractTypeListViewModel))
{
var vm = SimpleIoc.Default.GetInstance<ContractTypeListViewModel>();
vm.SelectedContractType = null;
}
}
Any ideas?
To remove the collection you can either set the SelectedItem property to null or clear the SelectedItems.
gridViewName.SelectedItem = null;
gridViewName.SelectedItems.Clear();
Without showing the code, we cannot precisely help you. A solution I think you can do is to implement the INotifyPropertyChanged interface in your view model and bind the selected item to a property of that type. Also check the output window if there is any binding failure.

dynamic tooltips on Winforms controls

I am looking for the cleanest way to bind the same datasource to a control's tooltip that I am binding to the control itself. For example, I have the line
control.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
where dataFeatures is of type BindingSource. I repeat similar lines for many controls on a WinForm Form. Some of these controls can adopt values whose text can span a greater text width than what is visible within the control itself. Instead of redesigning the layout of the form to account for the possibility of partially hidden text in some controls in a few situations, I would like to have the tooltip of each control be bound to the same property of the BindingSource as the controls' EditValue or Text property. Is this possible? I can imagine there is a way to do it by hand by handling the EditValueChanged event like I already do for different reasons, but I was hoping there would be a cleaner solution than having to add new lines of code for each control.
Anybody have a suggestion?
Thanks!
0. For DevExpress controls you can just bind DevExpressControl.ToolTip property to the same value:
devExpressControl.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
devExpressControl.DataBindings.Add(new Binding("ToolTip", dataFeatures, "Key", true, DataSourceUpdateMode.Never));
1. For standard WinForms controls you can use System.Windows.Forms.ToolTip component and its ToolTip.Popup event. For each control set its ToolTip to some value otherwise ToolTip will never appears:
control.DataBindings.Add(new Binding("Text", dataFeatures, "Key", true));
toolTip1.SetToolTip(control, "Some value");
Now you can use ToolTip.Popup event:
private bool _updatingToolTip;
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (_updatingToolTip) return;
//Get binding for Text property.
var binding = e.AssociatedControl.DataBindings["Text"];
if (binding == null) return;
//Get binding value.
var manager = binding.BindingManagerBase;
var itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true);
object value = itemProperty.GetValue(manager.Current);
string toolTipText;
if (value == null || string.IsNullOrEmpty(toolTipText = value.ToString()))
{
e.Cancel = true;
return;
}
//Update ToolTip text.
_updatingToolTip = true;
toolTip1.SetToolTip(e.AssociatedControl, toolTipText);
_updatingToolTip = false;
}
You can easily implement dynamic tooltips with the ToolTipController component. Put this component onto the Form, and assign to each editor via the BaseControl.ToolTipController property.
When it is done, you can handle the ToolTipController.BeforeShow event and change the text according to the control state. The active control is passed through the SelectedControl property of the event parameter.

How to programmatically select a TabItem in WPF TabControl

I would like to know how to select a specific TabItem in a WPF TabControl.
I tried these bellow but nothing work!
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabControl.SelectedValue = MyTabItem
MyTabItem.IsSelected = True
As #Chris says, any of the first three things should work and as #Phyxx says, it doesn't always really work. The problem is some subtle thing about the order of property changes. To work around it you need to let the WPF invoke your tab-selection code in its own time:
Dispatcher.BeginInvoke((Action)(() => MyTabControl.SelectedIndex = x));
This does just what Phyxx' timer does, but in a slightly less extreme way.
All your examples except the third one are correct and will work. The problem must be at another location. Maybe you reset the item after setting or your code never is called?
Valid
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabItem.IsSelected = True
Invalid
MyTabControl.SelectedValue = MyTabItem
Loop through the TabItems and for the tab to be selected, set
tabItem.IsSelected = true
If there are any other place due to binding changing you will see problem. Otherwise, the above code should work.
One thing which hasn't been mentioned above:
The main reason something like this won't work is that the tab items do not have the "Name" property set. Each tab item of the tab control which you want to navigate to programmatically must have its name property set for any of the above code to work.
<tabItem Name="tab1"></tabItem>
I have implemented a small MVVM bindings based solution for selecting tab panels pragmatically.
define a property in your view model - Selected int type
bind the property in your view
<TabControl
x:Name="TabsCandidate"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
SelectedIndex="{Binding Selected}"
private int _selected;
public int Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
Set the value to Select property, simply the binding will activate the tab panel.
if you want to navigate from tab panel inside parent tab panels, this solution will simply works, All you need to do is, access the data context of your control and set it
// set the property value of the view model which points the index of the tab controller.
((CandidateViewModel)((System.Windows.FrameworkElement)candidateTab.Content).DataContext).Selected = CandidateLogTabIndex;
Try to set the MyTabControl.SelectedIndex = x in the event handler of DataContextChanged or Loaded of your UI. Hope this will work.
I tried all the methods that should have worked, but like you nothing actually changed the selected tab. In the end I got it to work by putting the tab selection code in a DispatcherTimer tick.
DispatcherTimer switchTabTimer = new DispatcherTimer();
switchTabTimer.Interval = new TimeSpan(0);
switchTabTimer.Tick += (object timerSender, EventArgs timerE) =>
{
myTabControl.SelectedIndex = 0;
switchTabTimer.Stop();
};
switchTabTimer.Start();
if you don't know the index of the tab (hint its not TabIndex) use:
private async Task ChangeTabTo(TabItem wantedTab) {
int index = 0;
for (var i = 0; i < TabControl.Items.Count; i++) {
var tab = TabControl.Items[i];
var t = tab as TabItem;
if (t == null) continue;
if (t == wantedTab) {
index = i;
break;
}
}
await Dispatcher.BeginInvoke((Action)(() => TabControl.SelectedIndex = index));
}
or modify it to search by name if you don't want to keep a reference to the tab
I'm throwing my 2 cents on the topic, since it might help someone out. I'm using WPF with Prims framework.
I was unable to select a tab by binding to SelectedItem or SelectedIndex - it didn't work. I was also unable to set TabItem.Name value from within TabControl.ItemTemplate or TabControl.ContentTemplate.
Instead I implemented event-based solution:
Add Name value for my TabControl.
Create an event - in Prism that means define a class that derives from PubSubEvent<T> (T is the type of parameter - in my case that was the ViewModel object bound to the TabItem>.
Publish that event whenever I want to a tab to be selected.
Subscribe to the event within my View.cs class and set the TabControl.SelectedItem programmatically using FindName.

How do I implement a multicolumn ComboBox DataGridColumn in a WPF DataGrid?

I have a simple question that I assume does not have a simple solution. I need to have a multi-column ComboBox for some grid columns in my WPF DataGrid. Is there a known best-practice to accomplish this? From what I have gathered this will require subclassing the DataGridComboBoxColumn to support a custom ComboBox.
I have found some examples of this but not supporting EF entities (I'm using Code First EF).
Any advice is greatly appreciated. Thanks
NOTE: This is all done dynamically with C#. I'm not using XAML to define columns.
Update: What I mean by multicolumn is simply that when you drop the ComboBox down I need to show two values for "Display", even though behind the scenes of course I'm still just storing an ID.
See here:.
http://www.telerik.com/ClientsFiles/188010_multicolumn-dropdown.JPG
With the exception that I need to do this as a DataGridColumn that can be dynamically created and added to a grid, rather than just the simple combo shown in the image.
Update I finally managed to find an article on CodeProject where the author has developed a control with my -exact- requirements. It is located here. Now the only problem I am trying to solve is how to allow the control to work when using Entity Framework (specifically, code first). Getting closer!
I have found the solution for my particular scenario. I downloaded the custom multi-column ComboBox with the included DataGridComboBoxColumn subclass from the link in my last update above. Basically I just made this work with Entity Framework Code-First POCOs and it solved my problem. Here is what I had to do to make it work with POCOs.
Inside of the CustDataGridComboBoxColumn there are a few overrides. You just need to slightly modify the following two overrides. I’m using reflection to change set the property since I don’t know what it will be from the control.
The original implementation accomplished this by getting the correct Row from the DataRowView with SelectedValuePath.
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
DataGridCell cell = editingEventArgs.Source as DataGridCell;
if (cell != null)
{
// Changed to support EF POCOs
PropertyInfo info = editingElement.DataContext.GetType().GetProperty("YourPropertyName", BindingFlags.Public | BindingFlags.Instance);
object obj = info.GetValue(editingElement.DataContext, null);
comboBox.SelectedValue = obj;
}
return comboBox.SelectedItem;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
// Dynamically set the item on our POCO (the DataContext).
PropertyInfo info = editingElement.DataContext.GetType().GetProperty(“YourPropertyName”, BindingFlags.Public | BindingFlags.Instance);
info.SetValue(editingElement.DataContext, comboBox.SelectedValue, null);
return true;
}
Also, if you intend on creating this custom control completely in code dynamically instead of in XAML, you will have to add a setter to the Columns property because by default it is set to read-only.
//The property is default and Content property for CustComboBox
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableCollection<DataGridTextColumn> Columns
{
get
{
if (this.columns == null)
{
this.columns = new ObservableCollection<DataGridTextColumn>();
}
return this.columns;
}
set
{
this.columns = value;
}
}
Thanks for the views and answers provided. Sorry I was unable to adequately word the question to make more sense initially.
Can you clarify what you mean by multiple?
Are you looking to have something like either of these below?
[Column] (Combo) (Combo) (Combo) [Column]
or
[Column]
(Combo)
(Combo)
(Combo)
[Column]
If so you will need to implement a cell template for the column using the DataGridTemplateColumn Type.
http://windowsclient.net/wpf/wpf35/wpf-35sp1-toolkit-datagrid-feature-walkthrough.aspx
You can set this up in XAML and then just provide a collection to the grid for binding that will render the columns as needed.
what will be "YourPropertyName" means in :
PropertyInfo info = editingElement.DataContext.GetType().GetProperty("YourPropertyName", BindingFlags.Public | BindingFlags.Instance);

Resources