WPF Combobox SelectedItem hell - wpf

Im having a very funny issue in WPF
Im creating a Combobox through code, and adding it to a control.
When I set the Combobox.SelectedItem or Combobox.SelectedIndex or Combobox.SelectedValue I am unable to select another option from the Combox items.
ForeignKeyDisplayAttribute attribute = (ForeignKeyDisplayAttribute)this.GetAttribute(typeof(ForeignKeyDisplayAttribute), property);
if (attribute != null)
{
ForeignKeyDisplayAttribute fkd = attribute;
Type subItemType = fkd.ForeignKeyObject;
contentControl = new ComboBox();
object blankItem = System.Activator.CreateInstance(subItemType, new object[] { });
System.Reflection.MethodInfo method = subItemType.GetMethod("Load", new Type[] { typeof(int) });
object innerValue = method.Invoke(null, new object[] { value });
System.Collections.IList selectedSubItems = (System.Collections.IList)subItemType.GetMethod("Load", Type.EmptyTypes).Invoke(null, new object[] { });
selectedSubItems.Insert(0, blankItem);
((ComboBox)contentControl).SelectedValuePath = fkd.IdField;
((ComboBox)contentControl).DisplayMemberPath = fkd.DescriptionField;
((ComboBox)contentControl).ItemsSource = selectedSubItems;
((ComboBox)contentControl).InvalidateVisual();
// If I use any of the two below lines or SelectedItem then I can't change the value via the UI.
((ComboBox)contentControl).SelectedIndex = this.FindIndex(selectedSubItems, value);
((ComboBox)contentControl).SelectedValue = value;
}
Any idea's as to how I can fix this?

Well found the answer.
Turns out I had incorrectly coded the overridden object Equals method, and it was always returning false.
public override bool Equals(object obj)
{
if (obj is IId)
return this.Id.Equals(((IId)obj).Id);
return false;
}
should have been
public override bool Equals(object obj)
{
if (obj.GetType() == this.GetType() && obj is IId)
return this.Id.Equals(((IId)obj).Id);
return false;
}

Do you have Bindings on those ComboBox items in GUI? Then the simple reason is: setting a value manually in the code behind destroys the binding.
Workarround:
Before setting the value manually you can get the binding with the BindingOperations static functions.
Binding b = BindingOperations.GetBinding(yourComboBox, ComboBox.SelectedItemProperty);
// do your changes here
BindingOperations.SetBinding(yourComboBox, ComboBox.SelectedItemProperty, b);
Jan

Related

WPF DataGrid : TemplateColumn with UserControl and TextBlock

Im a bit stuck here.. been searching for two days and found no solution.
What i would like to achieve is the behavior of DataGridComboBoxColumn .
I need its DisplayMemberPath, SelectedValuePath and SelectedValueBinding properties..
Im having my UserControl similar to ComboBox in the CellEditingTemplate
and TextBlock in CellTemplate.
Im doing all these in code, since these columns may not exist necessarily..
This is where i define the TemplateColumn :
DataGridTemplateColumn tcol = new DataGridTemplateColumn();
tcol.Header = "accCust";
FrameworkElementFactory texttablF = new FrameworkElementFactory(typeof(TextTableBox));
texttablF.SetValue(TextTableBox.TableSourceProperty, accTable.DefaultView);
FrameworkElementFactory tb = new FrameworkElementFactory(typeof(TextBlock));
texttablF.SetBinding(TextBlock.TextProperty, new Binding("Account"));
tcol.CellTemplate = new DataTemplate(typeof(DataGridCell)) { VisualTree = tb };
tcol.CellEditingTemplate = new DataTemplate(typeof(DataGridCell)) { VisualTree = texttablF };
I have two issues :
TextBlock shows the ID present in the DataGrid's Table, I want it to show the value specified in the DisplayMemberPath, which i have no idea how to implement.
The DependencyProperty TableSource isnt working when done via FrameworkElement.setValue.. It works when it is done in the normal way,
ie
TextTableBox ttb = new TextTableBox();
ttb.TableSource = src_table.DefaultView;
I hope its clear to you what my problem is.. All i really want is to replace the ComboBox in DataGridComboColumn with my UserControl..
Thanks in advance :)
Im sorry if it is a poor question.. Im new to WPF and doing my HighSchools..
Because i wanted the Properties of ComboBox, i had to inherit from DataGridComboBoxColumn.
Heres how the code looks like :
public class DataGridTCBColumn : DataGridComboBoxColumn
{
private TableComboBox comboBox;
public DataGridTCBColumn(bool Editable)
{
comboBox = new TableComboBox() { IsEditable = Editable };
}
// Requires extra field..
public string SelectedValuePathX
{
get
{
return (string)this.GetValue(SelectedValuePathXProperty);
}
set
{
this.SetValue(SelectedValuePathXProperty, value);
}
}
static FrameworkPropertyMetadata SelectedValuePathXPropertyMeta = new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(SelectedValuePathXPropertyChanged));
public static readonly DependencyProperty SelectedValuePathXProperty =
DependencyProperty.Register("SelectedValueX", typeof(string), typeof(TableComboBox), SelectedValuePathXPropertyMeta);
private static void SelectedValuePathXPropertyChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e)
{}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if (e.Property == DataGridTCBColumn.ItemsSourceProperty)
{
comboBox.ItemsSource = ItemsSource;
}
else if (e.Property == DataGridTCBColumn.SelectedValuePathProperty)
{
comboBox.SelectedValuePath = SelectedValuePath;
}
else if (e.Property == DataGridTCBColumn.DisplayMemberPathProperty)
{
comboBox.DisplayMemberPath = DisplayMemberPath;
}
base.OnPropertyChanged(e);
}
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
return comboBox;
}
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
DataGridCell cell = editingEventArgs.Source as DataGridCell;
if (cell != null && !string.IsNullOrEmpty(this.SelectedValuePathX))
{
//For Typed DataSet
object obj = ((DataRowView)editingElement.DataContext).Row[this.SelectedValuePathX];
comboBox.SelectedValue = obj;
}
comboBox.Focus();
return comboBox.SelectedItem;
}
protected override bool CommitCellEdit(FrameworkElement editingElement)
{
if (!string.IsNullOrEmpty(this.SelectedValuePathX) && comboBox.SelectedValue != null)
((DataRowView)editingElement.DataContext).Row[this.SelectedValuePathX] = comboBox.SelectedValue;
return true;
}
}
Had to make another dependency propertry SelectedValuePathX. Actually, it does the job of SelectedValueBinding. However that Binding never did work (tried goin through the source code of ComboBox (Microsoft Reference Source) but dint work out well either.
Hopes it would help somebody :)

set ItemContainerStyle from code

I am working on a custom wpf control which is derived from a ListBox and am trying to apply some formatting to a custom property.
When a particular custom property is false, I want to apply some formatting to the ListBox.
I am using the following code to attempt to apply the styling -
var t = new Trigger();
var BackgroundSetter = new Setter {Property = BackgroundProperty, Value = null};
var BrushSetter = new Setter { Property = BorderBrushProperty, Value = null };
t.Setters.Add(BackgroundSetter);
t.Setters.Add(BrushSetter);
var s = new Style(typeof(ListBox));
s.Triggers.Add(t);
editor.ItemContainerStyle.Triggers.Add(t);
I have also tried the following with no luck -
editor.ItemContainerStyle = s;
I am getting an error that indicates that some object was not initialized and stepping through shows that editor.ItemContainerStyle is null.
The actual error message just says Exception has been thrown by the target of an invocation.
Does anyone have any idea what I might be doing wrong?
Thanks
I was able to get this working - below is the code that I actually ended up using -
public bool IsSelectable
{
get { return (bool)GetValue(IsSelectableProperty); }
set { SetValue(IsSelectableProperty, value); }
}
public static DependencyProperty IsSelectableProperty = DependencyProperty.Register("IsSelectable", typeof(bool), typeof(ListEditor), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(IsSelectablePropertyChanged)) { BindsTwoWayByDefault = true });
private static void IsSelectablePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var editor = sender as ListEditor;
var s = new Style(typeof(ListBoxItem));
var enableSetter = new Setter {Property = IsEnabledProperty, Value = editor.IsSelectable};
s.Setters.Add(enableSetter);
editor.ItemContainerStyle = s;
}

WPF ItemsControl ItemTemplate Validation

Is there a way to determine if an ItemsControl has any child controls with validation errors? I would like to bind this value (boolean) to the IsEnabled property on a Button.
I've recently used the code from this previous SO answer Detecting WPF Validation Errors to good effect for exactly this.
I have a user control which contains a DataGrid. The usercontrol exposes a IsValid property which contains a getter, that simply calls the static IsValid function passing in the DataGrid as the DependencyObject:
public class MyControl : UserControl
{
public bool IsValid
{
get { return Validator.IsValid(MyDataGrid); }
}
}
The control's IsValid property can then be checked by the CanExecute function of the command you bind to the button you want to enable/disable.
My only issue with the code that i linked to was that it actually evaluates the validations on the bindings, this means as soon as you run it any field that is technically invalid but hasn't yet been invalidated (i.e. the user may not have entered any data in that field yet because they haven't got to it) will now be in an invalid state - i haven't yet looked at a way to avoid or mitigate this.
Edit:
here is an updated version that doesn't invalidate the controls as i mentioned previously. I've simply commented out/slightly changed some lines but left everything in there so you can see the difference. Note that this should also perform faster as you will be exiting the moment you find the first invalid binding.
public static bool IsValid(DependencyObject parent)
{
// Validate all the bindings on the parent
bool valid = true;
LocalValueEnumerator localValues = parent.GetLocalValueEnumerator();
while (localValues.MoveNext())
{
LocalValueEntry entry = localValues.Current;
if (BindingOperations.IsDataBound(parent, entry.Property))
{
Binding binding = BindingOperations.GetBinding(parent, entry.Property);
foreach (ValidationRule rule in binding.ValidationRules)
{
ValidationResult result = rule.Validate(parent.GetValue(entry.Property), null);
if (!result.IsValid)
{
//BindingExpression expression = BindingOperations.GetBindingExpression(parent, entry.Property);
//System.Windows.Controls.Validation.MarkInvalid(expression, new ValidationError(rule, expression, result.ErrorContent, null));
//valid = false;
return false;
}
}
}
}
// Validate all the bindings on the children
for (int i = 0; i != VisualTreeHelper.GetChildrenCount(parent); ++i)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
if (!IsValid(child))
{
//valid = false;
return false;
}
}
//return valid;
return true;
}
I don't know why, slugter's answer didn't work for me (LocalValueEnumerator returned some properties but never the binded ones, like Text).
I managed to get it working with this code (derived from this answer):
public static bool IsValid(DependencyObject obj)
{
// The dependency object is valid if it has no errors,
//and all of its children (that are dependency objects) are error-free.
return !Validation.GetHasError(obj) &&
GetVisualTreeChildren(obj)
.OfType<DependencyObject>()
.All(child => IsValid(child));
}
//VisualTreeHelper don't have a method to get all the children of a visual object
private static IEnumerable GetVisualTreeChildren(DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
yield return VisualTreeHelper.GetChild(parent, i);
}

Inherited Control Visible/Enabled Property Value Always True: PropertyGrid

I have created a custom WinForms hosting environment. Which has a toolbox and a PropertyGrid.
The controls displayed in the Toolbox are inherited from existing WinForm controls.
DropDownList Source:
public interface IPropertyFilter : ICustomTypeDescriptor
{
PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection pdc);
List<string> GetPropertiesToShow();
}
[Serializable]
public class DropDownList : System.Windows.Forms.ComboBox, IPropertyFilter
{
public DropDownList()
{
}
#region IPropertyFilter Members
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(this, true);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(this, attributes, true);
}
EventDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetEvents()
{
return TypeDescriptor.GetEvents(this, true);
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(this, true);
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(this, true);
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this, attributes, true);
return FilterProperties(pdc);
}
PropertyDescriptorCollection System.ComponentModel.ICustomTypeDescriptor.GetProperties()
{
PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this, true);
return FilterProperties(pdc);
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(this, editorBaseType, true);
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(this, true);
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(this, true);
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(this, true);
}
public PropertyDescriptorCollection FilterProperties(PropertyDescriptorCollection pdc)
{
// Filter out properties that we do not want to display in PropertyGrid
return ControlDesignerHelper.GetBrowsableProperties(pdc, GetPropertiesToShow());
}
// Determines what properties of this control has to be shown in PropertyGrid
public List<string> GetPropertiesToShow()
{
// get a list of common properties that we want to show for all controls
List<string> browsableProps = ControlDesignerHelper.GetBasePropertiesToShow();
// add properties that are specific to this controls
browsableProps.Add("Items");
browsableProps.Add("AutoPostBack");
browsableProps.Add("AppendDataBoundItems");
browsableProps.Add("DataTextField");
browsableProps.Add("DataValueField");
return browsableProps;
}
#endregion
}
I have implemented ICustomTypeDescriptor to filter out properties that I do not want to show in the PropertyGrid.
Problem:
I am facing problem while serializing values of Enabled & Visible properties that are inherited from System.Windows.Forms.Control class.
WriteProperties Method (BasicDesignerLoader):
private void WriteProperties(XmlDocument document, PropertyDescriptorCollection properties, object value, XmlNode parent, string elementName)
{
foreach (PropertyDescriptor prop in properties)
{
System.Diagnostics.Debug.WriteLine(prop.Name);
if (prop.ShouldSerializeValue(value))
{
string compName = parent.Name;
XmlNode node = document.CreateElement(elementName);
XmlAttribute attr = document.CreateAttribute("name");
attr.Value = prop.Name;
node.Attributes.Append(attr);
DesignerSerializationVisibilityAttribute visibility = (DesignerSerializationVisibilityAttribute)prop.Attributes[typeof(DesignerSerializationVisibilityAttribute)];
switch (visibility.Visibility)
{
case DesignerSerializationVisibility.Visible:
if (!prop.IsReadOnly && WriteValue(document, prop.GetValue(value), node))
{
parent.AppendChild(node);
}
break;
case DesignerSerializationVisibility.Content:
object propValue = prop.GetValue(value);
if (typeof(IList).IsAssignableFrom(prop.PropertyType))
{
WriteCollection(document, (IList)propValue, node);
}
else
{
PropertyDescriptorCollection props = TypeDescriptor.GetProperties(propValue, propertyAttributes);
WriteProperties(document, props, propValue, node, elementName);
}
if (node.ChildNodes.Count > 0)
{
parent.AppendChild(node);
}
break;
default:
break;
}
}
}
}
Problem # 1: The ShouldSerializeValue method for the Enabled & Visible property always returns false.
Problem # 2: Even if I skip the ShouldSerializeValue method check the GetValue method of the PropertyDescriptor always returns True.
Current Workaround:
As a workaround I have currently made the Enabled & Visible properties hidden using the BrowsableAttribute, and created two other boolean properties and used the DisplayNameAttribute to change their display name to be Enable & Visible.
But for this workaround I have to write these snippets in every control.
Am I missing something or doing anything wrong? Why are the Enabled & Visible property do not change?
You will find a long discussion about this issue here. (dead link, can't find a new one)
This MSDN page aldo makes this remark:
The InheritedPropertyDescriptor class
modifies the default value of a
property, so that the default value is
the current value at object
instantiation. This is because the
property is inherited from another
instance. The designer defines
resetting the property value as
setting it to the value that was set
by the inherited class. This value may
differ from the default value stored
in metadata.
ShouldSerializeValue's return value is based on the difference between the current value and the default value so I think this is directly related to your problem.
I hope this will help you figure out what happens in your own context.

Winforms DataBind to Control's Visible Property

Are there any known issues when databinding to a control's visible property?
The control is always NOT visible regardless of what my property is.
Public ReadOnly Property IsRibbonCategory() As Boolean
Get
Return True
End Get
End Property
I tried the control's text property and other properties and they seem to work correctly.
I am trying to set a Panel's visible property.
I've found that life is better if you assume that binding to a control's Visible property is broken, despite the fact that it sometimes works. See http://support.microsoft.com/kb/327305, which says as much (and while the KB article applies to .NET 1.0 and 1.1, it still seems to be a problem in at least 2.0).
I created a utility class for creating bindings which, among other things, gave me a centralized place to add a work-around. Instead of actually creating a binding on Visible it does two things:
It subscribes to the data source's INotifyPropertyChanged.PropertyChanged event and sets the Visible value as appropriate when the event is raised.
It sets the initial value of Visible according to the current data source value.
This required a little reflection code, but wasn't too bad. It is critical that you don't bind the Visible property and do the work-around or it won't work.
Workaround: Set the Visible property on the BindingComplete event.
I had same issue setting a label's Visible property - always stays false, even though setting the Enabled property works fine.
I just hit this issue in .NET 4.7.1 and Visual Studio 2017. To fix it, I changed the Visible property on my control to be initially set to True, as I had it as False previously.
Things to check:
Be sure you've instantiated the class that has the IsRibbonCategory property
Did you set the datasource of property of the binding source to the instance of the class
The datasource update mode should be on "on validation"
Make sure you didn't set the visible property manually to false on the control
Hope that helps. Can you post more code?
A workaround would be to use a Component to databind to a control's visiblity property instead of directly binding to the control's visibility property.
See below code:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public class ControlVisibilityBinding : Component
{
private static readonly object EventControlChanged = new object();
private static readonly object EventVisibleChanged = new object();
private System.Windows.Forms.Control _control;
private bool _visible = true;
public event EventHandler VisibleChanged
{
add { Events.AddHandler(EventVisibleChanged, value); }
remove { Events.RemoveHandler(EventVisibleChanged, value); }
}
public event EventHandler ControlChanged
{
add { Events.AddHandler(EventControlChanged, value); }
remove { Events.RemoveHandler(EventControlChanged, value); }
}
public ControlVisibilityBinding()
{
}
public ControlVisibilityBinding(IContainer container)
{
container.Add(this);
}
[DefaultValue(null)]
public System.Windows.Forms.Control Control
{
get { return _control; }
set
{
if(_control == value)
{
return;
}
WireControl(_control, false);
_control = value;
if(_control != null)
{
_control.Visible = _visible;
}
WireControl(_control, true);
OnControlChanged(EventArgs.Empty);
OnVisibleChanged(EventArgs.Empty);
}
}
[DefaultValue(true)]
public bool Visible
{
get { return _visible; }
set
{
if(_visible != value)
{
_visible = value;
}
if(Control != null)
{
Control.Visible = _visible;
}
OnVisibleChanged(EventArgs.Empty);
}
}
private void WireControl(Control control, bool subscribe)
{
if(control == null)
{
return;
}
if(subscribe)
{
control.VisibleChanged += Control_VisibleChanged;
}
else
{
control.VisibleChanged -= Control_VisibleChanged;
}
}
private void Control_VisibleChanged(object sender, EventArgs e)
{
OnVisibleChanged(EventArgs.Empty);
}
protected virtual void OnVisibleChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventVisibleChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
protected virtual void OnControlChanged(EventArgs e)
{
EventHandler subscribers = (EventHandler)Events[EventControlChanged];
if(subscribers != null)
{
subscribers(this, e);
}
}
}
static class Program
{
[STAThread]
static void Main()
{
using(Form form = new Form())
using(FlowLayoutPanel groupBoxLayoutPanel = new FlowLayoutPanel())
using(RadioButton visibleButton = new RadioButton())
using(RadioButton hiddenButton = new RadioButton())
using(GroupBox groupBox = new GroupBox())
using(Label text = new Label())
using(ControlVisibilityBinding visibilityBinding = new ControlVisibilityBinding())
using(TextBox inputTextBox = new TextBox())
{
groupBoxLayoutPanel.Dock = DockStyle.Fill;
groupBoxLayoutPanel.FlowDirection = FlowDirection.LeftToRight;
groupBoxLayoutPanel.AutoSize = true;
groupBoxLayoutPanel.AutoSizeMode = AutoSizeMode.GrowAndShrink;
visibleButton.Text = "Show Label";
visibleButton.AutoSize = true;
hiddenButton.Text = "Hide Label";
hiddenButton.AutoSize = true;
groupBoxLayoutPanel.Controls.Add(visibleButton);
groupBoxLayoutPanel.Controls.Add(hiddenButton);
inputTextBox.Text = "Enter Label Text Here";
inputTextBox.Dock = DockStyle.Top;
groupBox.AutoSize = true;
groupBox.AutoSizeMode = AutoSizeMode.GrowAndShrink;
groupBox.Controls.Add(groupBoxLayoutPanel);
groupBox.Dock = DockStyle.Fill;
text.AutoSize = true;
text.ForeColor = Color.Red;
text.Dock = DockStyle.Bottom;
text.BorderStyle = BorderStyle.FixedSingle;
text.Font = new Font(text.Font.FontFamily, text.Font.Size * 1.25f, FontStyle.Bold | FontStyle.Italic);
text.DataBindings.Add("Text", inputTextBox, "Text", true, DataSourceUpdateMode.Never);
visibilityBinding.Control = text;
visibleButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
Binding binding = hiddenButton.DataBindings.Add("Checked", visibilityBinding, "Visible", true, DataSourceUpdateMode.OnPropertyChanged);
ConvertEventHandler invertConverter = (sender, e) => e.Value = !((bool)e.Value);
binding.Format += invertConverter;
binding.Parse += invertConverter;
form.Controls.Add(inputTextBox);
form.Controls.Add(text);
form.Controls.Add(groupBox);
Application.Run(form);
}
}
}
}
Here is my turn around, it may be stupid but it worked many times.
I put one Panel control in my form, I make it to Fill my form and I put everything in that Panel. All the controls I bind the Visible property see their visibility change according to the objects in my DataGridView.

Resources