PropertyGrid - Specify ExpandableObject if I don't have control of the class - c#

I try to use a PropertyGrid (actually, it's the xceed wpf toolkit propertygrid, but I can switch to the standard forms PropertyGrid if that makes it easier), and the object I need to show in the grid has some child-objects that I need to be able to expand.
I found out I can achieve this by marking the properties with the "ExpandableObject" attribute. However, in some cases I am not the author of the class (or I am, but don't want to clutter it with GUI-stuff), so I cannot add attributes like that.
Is there any other way to tell the PropertyGrid which properties that should be expandable?

Xceed property grid has an event PreparePropertyItem. You can handle it and set e.PropertyItem.IsExpandable property. There is an example of handler that makes all non primitive properties expandable:
private void propertyGrid_PreparePropertyItem(object sender, Xceed.Wpf.Toolkit.PropertyGrid.PropertyItemEventArgs e)
{
var item = e.Item as Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem;
if (item == null)
return;
if (!item.PropertyType.IsValueType && item.PropertyType != typeof(string))
{
e.PropertyItem.IsExpandable = true;
}
}

Related

Why does OnDrawItem event for a ListView not affect the Design-time environment?

If I create a class and make it derive from a ListView like this...
class MyListView : ListView
{
public MyListView() : base()
{
DoubleBuffered = true;
OwnerDraw = true;
Cursor = Cursors.Hand;
Scrollable = false;
}
protected override void OnDrawItem(DrawListViewItemEventArgs e)
{
//base.OnDrawItem(e);
}
}
Then I open the design view of my windows form and add a new MyListView object then add a single item and link it to a image list. I can see that there is one item in the mylistview object. It has no effect on the object I have on my form called lv of type MyListView. When I run my app on the other hand I see exactly what I expected and there is no items listed.
Why would this effect run-time and not design-time painting?
The answer
ListViewDesigner shadows OwnerDraw property like Visible or Enabled property of control. So it just works at run-time and changing it doesn't affect design-time.
Side Note
If you take a look at source code of ListViewDesigner, you will see this property:
private bool OwnerDraw
{
get { return (bool) base.ShadowProperties["OwnerDraw"]; }
set { base.ShadowProperties["OwnerDraw"] = value; }
}
And in PreFilterProperties you will see the designer replaced the original property with this one:
PropertyDescriptor oldPropertyDescriptor = (PropertyDescriptor) properties["OwnerDraw"];
if (oldPropertyDescriptor != null)
{
properties["OwnerDraw"] = TypeDescriptor.CreateProperty(typeof(ListViewDesigner),
oldPropertyDescriptor, new Attribute[0]);
}
So it doesn't matter what View you use, it performs the default painting regardless of what you have in OnDrawItem. It's because it doesn't use OwnerDraw property at design-time. The designer shadows it. This is the same behavior which you see for Enabled or Visible property.
Workaround to enable owner-draw at run-time
As a workaround, you can register a different Designer for your derived control. This way the OwnerDraw property will work as a normal property:
[Designer(typeof(ControlDesigner))]
public class MyListView : ListView
Warning: Keep in mind, by registering a new designer for the control, you will lose the current ListViewDesigner features like its designer verbs or its smart tag (actions list) window or Column Sizing options. If you need those features, you can implement those features in a custom designer by looking into ListViewDesigner source code.

WPF PropertyGrid supports multiple selection

Is this documentation still valid or am I missing something?
http://doc.xceedsoft.com/products/XceedWpfToolkit/Xceed.Wpf.Toolkit~Xceed.Wpf.Toolkit.PropertyGrid.PropertyGrid~SelectedObjects.html
PropertyGrid control does not appear to have SelectedObjects or SelectedObjectsOverride members. I'm using the latest version (2.5) of the Toolkit against .NET Framework 4.0.
UPDATE
#faztp12's answer got me through. For anyone else looking for a solution, follow these steps:
Bind your PropertyGrid's SelectedObject property to the first selected item. Something like this:
<xctk:PropertyGrid PropertyValueChanged="PG_PropertyValueChanged" SelectedObject="{Binding SelectedObjects[0]}" />
Listen to PropertyValueChanged event of the PropertyGrid and use the following code to update property value to all selected objects.
private void PG_PropertyValueChanged(object sender, PropertyGrid.PropertyValueChangedEventArgs e)
{
var changedProperty = (PropertyItem)e.OriginalSource;
foreach (var x in SelectedObjects) {
//make sure that x supports this property
var ProperProperty = x.GetType().GetProperty(changedProperty.PropertyDescriptor.Name);
if (ProperProperty != null) {
//fetch property descriptor from the actual declaring type, otherwise setter
//will throw exception (happens when u have parent/child classes)
var DeclaredProperty = ProperProperty.DeclaringType.GetProperty(changedProperty.PropertyDescriptor.Name);
DeclaredProperty.SetValue(x, e.NewValue);
}
}
}
Hope this helps someone down the road.
What i did when i had similar problem was I subscribed to PropertyValueChanged and had a List filled myself with the SelectedObjects.
I checked if the contents of the List where of the same type, and then if it is so, I changed the property in each of those item :
PropertyItem changedProperty = (PropertyItem)e.OriginalSource;
PropertyInfo t = typeof(myClass).GetProperty(changedProperty.PropertyDescriptor.Name);
if (t != null)
{
foreach (myClass x in SelectedItems)
t.SetValue(x, e.NewValue);
}
I used this because i needed to make a Layout Designer and this enabled me change multiple item's property together :)
Hope it helped :)
Ref Xceed Docs

C# Dynamic form (reflection) - linking controls

Sorry for the poor quality of the title. I couldn't think of a better way to phrase this.
For a project I'm currently working on with a few friends, I got myself in the situation where I have created a dynamic form (with reflection) which I now want to validate.
Example (ignore the black box, it contains old form elements which are now irrelevant and i didn't want to confuse you guys):
As you may have guessed already, it is an application for creating a mysql database.
Which is where I get to my problem(s). I want to disable checkboxes if others are checked.
For example: If I check "PrimaryKey" I want to disable the checkbox "Null".
Changing from unsigned to signed changes the numericupdown minimum and maximum etc.
But with reflection and all, I find it difficult to know exactly which checkbox to disable.
I was hoping you guys would have some suggestions.
I have been thinking about this for a while and a few thoughts have come to mind. Maybe these are better solutions than the current one.
Thought 1: I create UserControls for every datatype. Pro's: no problems with reflection and easy identifying of every control in the UserControl for validation. Con's: Copy-Pasting, Lots of UserControls, with a lot of the same controls.
Thought 2: Doing something with the description tags for every property of the classes. Creating rules in the description that allow me to link the checkboxes together. Here I'll only have to copy the rules to every class property and then it should be ok.
I had been thinking of other solutions but I failed to remember them.
I hope you guys can give me a few good pointers/suggestions.
[Edit]
Maybe my code can explain a bit more.
My code:
PropertyInfo[] properties = DataTypes.DataTypes.GetTypeFromString(modelElement.DataType.ToString()).GetType().GetProperties();
foreach (PropertyInfo prop in properties)
{
if (prop.Name != "Label" && prop.Name != "Project" && prop.Name != "Panel")
{
var value = prop.GetValue(modelElement.DataType, null);
if (value != null)
{
tableLayoutPanel1.Controls.Add(new Label { Text = prop.Name, Anchor = AnchorStyles.Left, AutoSize = true });
switch (value.GetType().ToString())
{
case "System.Int32":
NumericUpDown numericUpDown = new NumericUpDown();
numericUpDown.Text = value.ToString();
numericUpDown.Dock = DockStyle.None;
tableLayoutPanel1.Controls.Add(numericUpDown);
break;
case "System.Boolean":
CheckBox checkBox = new CheckBox();
checkBox.Dock = DockStyle.None;
// checkbox will become huge if not for these changes
checkBox.AutoSize = false;
checkBox.Size = new Size(16, 16);
if (value.Equals(true))
{
checkBox.CheckState = CheckState.Checked;
}
tableLayoutPanel1.Controls.Add(checkBox);
break;
default:
MessageBox.Show(#"The following type has not been implemented yet: " + value.GetType());
break;
}
}
}
}
Here is a mockup from my comments:
// The ViewModel is responsible for handling the actual visual layout of the form.
public class ViewModel {
// Fire this when your ViewModel changes
public event EventHandler WindowUpdated;
public Boolean IsIsNullCheckBoxVisible { get; private set; }
// This method would contain the actual logic for handling window changes.
public void CalculateFormLayout() {
Boolean someLogic = true;
// If the logic is true, set the isNullCheckbox to true
if (someLogic) {
IsIsNullCheckBoxVisible = true;
}
// Inform the UI to update
UpdateVisual();
}
// This fires the 'WindowUpdated' event.
public void UpdateVisual() {
if (WindowUpdated != null) {
WindowUpdated(this, new EventArgs());
}
}
}
public class TheUI : Form {
// Attach to the viewModel;
ViewModel myViewModel = new ViewModel();
CheckBox isNullCheckBox = new CheckBox();
public TheUI() {
this.myViewModel.WindowUpdated += myViewModel_WindowUpdated;
}
void myViewModel_WindowUpdated(object sender, EventArgs e) {
// Update the view here.
// Notie that all we do in the UI is to update the visual based on the
// results from the ViewModel;
this.isNullCheckBox.Visible = myViewModel.IsIsNullCheckBoxVisible;
}
}
The basic idea here is that you ensure that the UI does as little as possible. It's role should just be to update. Update what? That's for the ViewModel class to decide. We perform all of the updating logic in the ViewModel class, and then when the updating computations are done, we call the UpdateVisual() event, which tells the UI that it needs to represent itself. When the WindowUpdated Event occurs, the UI just responds by displaying the configuration set up by the ViewModel.
This may seem like a lot of work to set up initially, but once in place it will save you tons and tons of time down the road. Let me know if you have any questions.
Try relating the event of one checkbox to disable the other; something like this:
private void primaryKeyBox_AfterCheck(object sender, EventArgs e)
{
nullBox.Enabled = false;
}
This is a very simple example and would have to be changed a bit, but for what I think you're asking it should work. You would also have to add to an event for the boxes being unchecked. You would also need logic to only get data from certain checkboxes based on the ones that are and are not checked.
For all the other things, such as changing the numbers based on the dropdown, change them based on events as well.
For WinForms I would use data binding.
Create an object and implement INotifyPropertyChanged and work with that object.
Then, If you have an object instance aObj:
To bind the last name property to a textbox on the form do this:
Private WithEvents txtLastNameBinding As Binding
txtLastNameBinding = New Binding("Text", aObj, "LastName", True, DataSourceUpdateMode.OnValidation, "")
txtLastName.DataBindings.Add(txtLastNameBinding)
Take a look here for more info.
INotifyPropertyChanged

How to hide part of a WPF control conditionally?

I have a control in an assembly that I can't change that is very similar to the .NET DateTimePicker. I want to hide the time picker portion of that control when a certain condition is met (Property value on my ViewModel). The control looks like this:
[TemplatePart(Name = "PART_DatePicker", Type = typeof (DatePicker))]
[TemplatePart(Name = "PART_TimePicker", Type = typeof (TimePicker))]
public class MyDateTimePicker : Control {/*...*/}
This answer shows a nice way to always hide a PART of a control, but I want to do it dynamically:
How to hide a part of a WPF control
I imagine there are a few ways to do this. What I want is something minimal (like in the linked question's answer) as well as something that doesn't violate MVVM. System.Interactivity behaviors and triggers are fair game.
Create a new control extending the previous one
public sealed class MySuperiorDateTimePicker : MyDateTimePicker
{
//....
Add a DependencyProperty that you can bind to your ViewModel's state
public static readonly DependencyProperty HideItProperty =
DependencyProperty.Register(
"HideIt",
typeof(bool),
typeof(MySuperiorDateTimePicker ),
new UIPropertyMetadata(false, HideItPropertyChanged));
//snip property impl
Wait for the property to change, then hide your UI
private static void HideItPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
(d as MySuperiorDateTimePicker).OnHideItChanged((bool)e.OldValue,
(bool)e.NewValue);
}
private void OnHideItChanged(bool oldValue, bool newValue)
{
if(BusyTemplate == null)
return;
FindTimePicker().Visibility = newValue ? Visibility.Visible :
Visibility.Collapsed;
}
private UIElement FindTimePicker()
{
//snip null checks
return GetTemplateChild("PART_TimePicker") as UIElement;
}
Be careful with FindTimePicker as your DP might change before the control is loaded, and GetTemplateChild will return null. The usual thing to do is, in OnHideItChanged, if GetTemplateChild returns null use Dispatcher.BeginInvoke to re-run the event handler later on (ApplicationIdle or earlier).
When you find yourself saying "How can I do UI work using MVVM" stop and rethink your true goals. MVVM != no codebehind, no custom controls, etc.
One solution would be to hide it with the help of a DataTrigger defined in the datatemplate, so that when a certain value in the datacontext of the control is set to true/false then you will hide/show the part.
A quick search and i found some links that you might find useful:
http://zamjad.wordpress.com/2010/06/22/conditionally-hide-controls-from-data-template/
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/ae2dbfb7-5dd6-4352-bfa1-53634289329d/
The solution that worked for me was to edit the style of the control. Using Blend, I edited a copy of the style of the DateTimePicker, and added a binding to Visibility of the TimePicker that looks at my VM and converts the value of the enumeration.

Sharing non control related data between WPF ViewModel and UserControl

I'm new to WPF and the MVVM pattern so keep that in mind.
The project I'm tasked with working on has a view and a view model. The view also contains a user control that does NOT have a view model. There is data (custom object ... Order) being passed to the view model that I also need to share with the user control.
It looks like the UserControl does share data between the view model already via DependencyPropertys but this data is just text boxes on the user control that look to be bound back to propertys on the view model.
I need to share data that will NOT be represented by a control on the user control. Is there a good way to pass this data (complex Order object)? Maybe I do need some kind of hidden control on my user control to accomplish this but I'm just not that sure being new to this. Any advice would be appreciated.
There is no need for hidden fields (or any such concept in WPF) as you can add any custom properties you want to a user control.
In the user control, create a new dependancy property like this but with MyUserControl set apropriately:
public Order CurrentOrder
{
get { return (Order)GetValue(CurrentOrderProperty); }
set { SetValue(CurrentOrderProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentOrder. This enables binding, etc.
public static readonly DependencyProperty CurrentOrderProperty =
DependencyProperty.Register("CurrentOrder", typeof(Order), typeof(MyUserControl), new PropertyMetadata(null, OnCurrentOrderPropertyChanged));
public static void OnCurrentOrderPropertyChanged(DependencyObject Sender, DependencyPropertyChangedEventArgs e)
{
var sender = Sender as MyUserControl;
var NewValue = e.NewValue as Order;
var OldValue = e.OldValue as Order;
if (OldValue != null && sender != null)
{
//Use old value as needed and use sender instead of this as method is static.
}
if (NewValue != null && sender != null)
{
//Use new value as needed and use sender instead of this as method is static.
}
}
In you're parent view where you use the usercontrol you then write something like:
<local:MyUserControl CurrentOrder="{Binding ViewModelOrder}" />
Where CurrentOrder is the dependancy property on the usercontrol and ViewModelOrder is the name of the property in the view model you would need to replace local:MyUserControl with your control name/namespace.
You can simply create a dependency property in the class of your UserControl and bind to it in the View that uses the control. There is no need to internally bind the dependency property to one of the controls in the UserControl.

Categories