xaml Convert ID to String without using ComboBox - wpf

I've been using code like this
<ComboBox ItemsSource="{Binding Path=CompaniesViewModel.CompaniesCollection}"
SelectedValuePath="CompanyId"
SelectedValue="{Binding Path=CompanyId}"
IsEnabled="False"
DisplayMemberPath="CompanyName"
/>
To display a Company Name in a ComboBox. Notice how the IsEnabled is set to false...that's because I really don't want the user to use the ComboBox. I am just using it as an easy way to convert an ID to string for display purposes.
When I put items in a Grid and there are a lot of them, I think it is really hurting the rendering performance. When I remove the ComboBox it loads in a split second. When the ComboBox is used in the code it can take 20 seconds.
I guess my question is I think I should be using a Label or TextBlock but not sure how to get the binding to work correctly as They don't have an ItemsSource or a SelectedValuePath or SelectedValue.
I thought about writing an IValueConverter but not sure how to bind/pass in the 3 values. I'd have to pass in the collection, the ValuePath and the Value ID.
Any thoughts or suggestions?

Put a
public Company Company {get {return CompaniesCollection.FirstOrDefault(x => x.CompanyId == CompanyId); }}
property in the ViewModel.

I welcome any of you to examine my code to see if you can make it more efficient but this is what I ended up doing.
<cc:LookupLabel
ItemsSource="{Binding Path=CompaniesCollection}"
SelectedValuePath="CompanyId"
SelectedValue="{Binding Path=CompanyId}"
DisplayMemberPath="CompanyName"
/>
And below is the LookupLabel derived from Label and INotifyPropertyChanged. I'm not sure how Microsoft implements this efficiently but this was my best stab at it. In particular the GetContent method listed at the bottom. All the other stuff is just the messy DependencyProperty declarations.
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace CustomControls
{
public class LookupLabel : Label, INotifyPropertyChanged
{
public LookupLabel()
{
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region ItemsSource
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LookupLabel)
, new UIPropertyMetadata(null, LookupLabel.ItemsSourceChanged)
);
private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("ItemsSource");
t.Content = GetContent(t);
}
[Bindable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource
{
get
{
return (IEnumerable)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
}
}
#endregion ItemsSource
#region SelectedValue
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(LookupLabel)
, new UIPropertyMetadata(null, LookupLabel.SelectedValueChanged)
);
private static void SelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("SelectedValue");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public object SelectedValue
{
get
{
return (object)GetValue(SelectedValueProperty);
}
set
{
SetValue(SelectedValueProperty, value);
}
}
#endregion SelectedValue
#region SelectedValuePath
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LookupLabel)
, new UIPropertyMetadata(string.Empty, LookupLabel.SelectedValuePathChanged)
);
private static void SelectedValuePathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("SelectedValuePath");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public string SelectedValuePath
{
get
{
return (string)GetValue(SelectedValuePathProperty);
}
set
{
SetValue(SelectedValuePathProperty, value);
}
}
#endregion SelectedValuePath
#region DisplayMemberPath
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LookupLabel)
, new UIPropertyMetadata(string.Empty, LookupLabel.DisplayMemberPathChanged)
);
private static void DisplayMemberPathChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LookupLabel t = d as LookupLabel;
t.NotifyPropertyChanged("DisplayMemberPath");
t.Content = GetContent(t);
}
[Localizability(LocalizationCategory.NeverLocalize)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Bindable(true)]
[Category("Appearance")]
public string DisplayMemberPath
{
get
{
return (string)GetValue(DisplayMemberPathProperty);
}
set
{
SetValue(DisplayMemberPathProperty, value);
}
}
#endregion DisplayMemberPath
protected static object GetContent(LookupLabel label)
{
if (label.ItemsSource == null)
{
return null;
}
if (string.IsNullOrWhiteSpace(label.SelectedValuePath))
{
return null;
}
if (string.IsNullOrWhiteSpace(label.DisplayMemberPath))
{
return null;
}
if (label.SelectedValue == null)
{
return null;
}
object result = null;
System.Reflection.PropertyInfo valuePropertyInfo = null;
foreach (var item in label.ItemsSource)
{
if (valuePropertyInfo == null)
{
valuePropertyInfo = item.GetType().GetProperty(label.SelectedValuePath);
if (valuePropertyInfo == null)
{
return null;
}
}
if (valuePropertyInfo.GetValue(item, null).Equals(label.SelectedValue))
{
var displayPropertInfo = item.GetType().GetProperty(label.DisplayMemberPath);
if (displayPropertInfo == null)
{
return null;
}
else
{
result = displayPropertInfo.GetValue(item, null);
break;
}
}
}
return result;
}
}
}

I am guessing that your combobox is taking too long to load because your collection has a lot of items.
You shouldn't be loading all your companies if you'll just show one of them, as a general good-practice.
I don't quite grasp your intent in using the combobox. Is it the style? can it be enabled in the future?
If it is only an easy way of displaying the CompanyName then i'd suggest the following:
Bind directly to CompanyName property.
-OR-
in the code-behind, create a calculated property named "CompanyDisplayName" that gets your company name.
Bind to it in the XAML
in the code-behind, whenever the current selected Company instance or the CompanyId changes fire 'OnPropertyChanged("CompanyDisplayName")
Try a TextBlock or a readonly TextBox to enable copy/paste;
For more info on the NotifyPropertyCahnged paradigm read here

Related

WPF Combobox SelectedItem not in Itemssource

How do you get the SelectedItem of a ComboBox to show even when it is not in the ItemsSource?
just as a simple example...
Suppose I have a "Class" object with a "Teacher" property on it.
public class Class: INotifyPropertyChanged
{
private Individual _teacher
public Individual Teacher
{
get { return _teacher; }
set
{
teacher = value;
RaisePropertyChanged("Teacher");
}
}
...
}
On the "Maintain Classes" GUI, there is a ComboBox to select a Teacher, and I only want active individuals to show up in the ComboBox. And I don't want users to be able to type free form text into the ComboBox. To achieve this, I bind ItemsSource to a collection in my ViewModel that only includes active individuals and the SelectedItem bound to a Teacher property of my "Class" object.
public class MaintainClasses_ViewModel:INotifyPropertyChanged
{
private ObservableCollection<Individual> _activeIndividuals
= GetAllActiveIndividuals();
public ObservableCollection<Individual> ActiveIndividuals
{
get { return _activeIndividuals
}
public Class SelectedClass
{
get;
set;
}
}
with the xaml for my ComboBox being...
<ComboBox ItemsSource="{Binding ActiveIndividuals}"
SelectedItem="{Binding SelectedClass.Teacher}" />
Now suppose I open the "Maintain Classes" GUI for a class where the teacher that has already been saved with is now inactive. Now... I want only active individuals to show up in the combobox -PLUS the teacher that was previously selected (even though they are now inactive and NOT in the ItemsSource).
Currently, the only way I have found to do this is to add the Inactive Individual to the collection and raise the PropertyChanged event for the collection. However, I would really like to archive this result without adding things to the collection. Preferably some method that uses xaml, selectors, and/or converters.
Here is what I've been using, I hope it Helps:
ComboBoxAdaptor.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
namespace Adaptors
{
[ContentProperty("ComboBox")]
public class ComboBoxAdaptor : ContentControl
{
#region Protected Properties
protected bool IsChangingSelection
{get; set;}
protected ICollectionView CollectionView
{get; set;}
#endregion
#region Dependency Properties
public static readonly DependencyProperty ComboBoxProperty =
DependencyProperty.Register("ComboBox", typeof(ComboBox), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ComboBox_Changed)));
public ComboBox ComboBox
{
get { return (ComboBox)GetValue(ComboBoxProperty);}
set { SetValue(ComboBoxProperty, value);}
}
public static readonly DependencyProperty NullItemProperty =
DependencyProperty.Register("NullItem", typeof(object), typeof(ComboBoxAdaptor),
new PropertyMetadata("(None)");
public object NullItem
{
get {return GetValue(NullItemProperty);}
set {SetValue(NullItemProperty, value);}
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(new PropertyChangedCallback(ItemsSource_Changed)));
public IEnumerable ItemsSource
{
get {return (IEnumerable)GetValue(ItemsSourceProperty);}
set {SetValue(ItemsSourceProperty, value);}
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(ComboBoxAdaptor),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
new PropertyChangedCallback(SelectedItem_Changed)));
public object SelectedItem
{
get {return GetValue(SelectedItemProperty);}
set {SetValue(SelectedItemProperty, value);}
}
public static readonly DependencyProperty AllowNullProperty =
DependencyProperty.Register("AllowNull", typeof(bool), typeof(ComboBoxAdaptor),
new PropertyMetadata(true, AllowNull_Changed));
public bool AllowNull
{
get {return (bool)GetValue(AllowNullProperty);}
set {SetValue(AllowNullProperty, value);}
}
#endregion
#region static PropertyChangedCallbacks
static void ItemsSource_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
adapter.Adapt();
}
static void AllowNull_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
adapter.Adapt();
}
static void SelectedItem_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ComboBoxAdaptor adapter = (ComboBoxAdaptor)d;
if (adapter.ItemsSource != null)
{
//If SelectedItem is changing from the Source (which we can tell by checking if the
//ComboBox.SelectedItem is already set to the new value), trigger Adapt() so that we
//throw out any items that are not in ItemsSource.
object adapterValue = (e.NewValue ?? adapter.NullItem);
object comboboxValue = (adapter.ComboBox.SelectedItem ?? adapter.NullItem);
if (!object.Equals(adapterValue, comboboxValue))
{
adapter.Adapt();
adapter.ComboBox.SelectedItem = e.NewValue;
}
//If the NewValue is not in the CollectionView (and therefore not in the ComboBox)
//trigger an Adapt so that it will be added.
else if (e.NewValue != null && !adapter.CollectionView.Contains(e.NewValue))
{
adapter.Adapt();
}
}
}
#endregion
#region Misc Callbacks
void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ComboBox.SelectedItem == NullItem)
{
if (!IsChangingSelection)
{
IsChangingSelection = true;
try
{
int selectedIndex = ComboBox.SelectedIndex;
ComboBox.SelectedItem = null;
ComboBox.SelectedIndex = -1;
ComboBox.SelectedIndex = selectedIndex;
}
finally
{
IsChangingSelection = false;
}
}
}
object newVal = (ComboBox.SelectedItem == null? null: ComboBox.SelectedItem);
if (!object.Equals(SelectedItem, newVal)
{
SelectedItem = newVal;
}
}
void CollectionView_CurrentChanged(object sender, EventArgs e)
{
if (AllowNull && (ComboBox != null) && (((ICollectionView)sender).CurrentItem == null) && (ComboBox.Items.Count > 0))
{
ComboBox.SelectedIndex = 0;
}
}
#endregion
#region Methods
protected void Adapt()
{
if (CollectionView != null)
{
CollectionView.CurrentChanged -= CollectionView_CurrentChanged;
CollectionView = null;
}
if (ComboBox != null && ItemsSource != null)
{
CompositeCollection comp = new CompositeCollection();
//If AllowNull == true, add a "NullItem" as the first item in the ComboBox.
if (AllowNull)
{
comp.Add(NullItem);
}
//Now Add the ItemsSource.
comp.Add(new CollectionContainer{Collection = ItemsSource});
//Lastly, If Selected item is not null and does not already exist in the ItemsSource,
//Add it as the last item in the ComboBox
if (SelectedItem != null)
{
List<object> items = ItemsSource.Cast<object>().ToList();
if (!items.Contains(SelectedItem))
{
comp.Add(SelectedItem);
}
}
CollectionView = CollectionViewSource.GetDefaultView(comp);
if (CollectionView != null)
{
CollectionView.CurrentChanged += CollectionView_CurrentChanged;
}
ComboBox.ItemsSource = comp
}
}
#endregion
}
}
How To Use It In Xaml
<adaptor:ComboBoxAdaptor
NullItem="Please Select an Item.."
ItemsSource="{Binding MyItemsSource}"
SelectedItem="{Binding MySelectedItem}">
<ComboBox Width="100" />
</adaptor:ComboBoxAdaptor>
Some Notes
If SelectedItem changes to a value not in the ComboBox, it will be added to the ComboBox (but not the ItemsSource). The next time SelectedItem is changed via Binding, any items not in ItemsSource will be removed from the ComboBox.
Also, the ComboBoxAdaptor allows you to insert a Null item into the ComboBox. This is an optional feature that you can turn off by setting AllowNull="False" in the xaml.

WPF Datagrid: MVVM friendly way to bind SelectedCells to my ViewModel

I'm using the WPF datagrid, and have SelectionUnit="Cell" and SelectionMode="Extended". I'm also trying to adhere to the MVVM principals as much as I can.
I need my ViewModel to keep track of the current SelectedCells.
Life would be easy if I could just Bind its SelectedCells property to my ViewModel. Oddly enough, SelectedCells is only raised once - when we first select any cell in the grid.
MS explains it here: http://social.msdn.microsoft.com/Forums/en/wpf/thread/737117f4-6d20-4232-88cf-e52cc44d4431
Can anyone think of an MVVM-friendly approach to get around it?
Thanks!
I realized my last answer was for SelectedItems instead of SelectedCells, so I wrote a complete attached property class to do data binding for multiple SelectedCells which works as follows:
<controls:DataGrid ItemsSource="{StaticResource list}"
SelectionMode="Extended"
behaviors:DataGridSelectedCellsBehavior.SelectedCells="{Binding Path=SelectedGridCellCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I have a working source code and a demo project of it here.
Attached property behavior Code :
public class DataGridSelectedCellsBehavior
{
// Source : https://archive.codeplex.com/?p=datagridthemesfromsl
// Credit to : T. Webster, https://stackoverflow.com/users/266457/t-webster
public static IList<DataGridCellInfo> GetSelectedCells(DependencyObject obj)
{
return (IList<DataGridCellInfo>)obj.GetValue(SelectedCellsProperty);
}
public static void SetSelectedCells(DependencyObject obj, IList<DataGridCellInfo> value)
{
obj.SetValue(SelectedCellsProperty, value);
}
public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.RegisterAttached("SelectedCells", typeof(IList<DataGridCellInfo>), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null, OnSelectedCellsChanged));
static SelectedCellsChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectedCellsChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectedCellsChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty = DependencyProperty.RegisterAttached("SelectedCellsChangedEventHandler", typeof(SelectedCellsChangedEventHandler), typeof(DataGridSelectedCellsBehavior), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is DataGrid)//DataGrid
{
DataGrid datagrid = d as DataGrid;
SelectedCellsChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedCells(d) as IList<DataGridCellInfo>)
datagrid.SelectedCells.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedCells(d, datagrid.SelectedCells);
};
SetSelectionChangedHandler(d, selectionchanged);
datagrid.SelectedCellsChanged += GetSelectionChangedHandler(d);
}
//else if (d is ListBox)
//{
// ListBox listbox = d as ListBox;
// SelectionChangedEventHandler selectionchanged = null;
// selectionchanged = (sender, e) =>
// {
// SetSelectedCells(d, listbox.SelectedCells);
// };
// SetSelectionChangedHandler(d, selectionchanged);
// listbox.SelectionChanged += GetSelectionChangedHandler(d);
//}
}
}
View Model Code :
class DemoViewModel : INotifyPropertyChanged
{
private IList<DataGridCellInfo> selectedGridCellCollection = new List<DataGridCellInfo>();
public IList<DataGridCellInfo> SelectedGridCellCollection
{
get { return selectedGridCellCollection; }
set
{
selectedGridCellCollection = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Do you need the SelectedCells constantly data-binded, or just when the user hits the OK/Accept button? If you only need it at the end of whatever process the user is in you can bind the SelectedCells to the CommandParameter property of a Button, for example. The SelectedCells is an IList, and you know enough to just do a cast to whatever object type the selection actually is. The other option is messier, you can use an attached property, keeping the event-handling out of your Views. This attached property would handle either a ListBox or in your case a DataGrid (MultiSelector).
public class Attach
{
public static IList GetSelectedItems(DependencyObject obj)
{
return (IList)obj.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject obj, IList value)
{
obj.SetValue(SelectedItemsProperty, value);
}
/// <summary>
/// Attach this property to expose the read-only SelectedItems property of a MultiSelector for data binding.
/// </summary>
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(Attach), new UIPropertyMetadata(new List<object>() as IList, OnSelectedItemsChanged));
static SelectionChangedEventHandler GetSelectionChangedHandler(DependencyObject obj)
{
return (SelectionChangedEventHandler)obj.GetValue(SelectionChangedHandlerProperty);
}
static void SetSelectionChangedHandler(DependencyObject obj, SelectionChangedEventHandler value)
{
obj.SetValue(SelectionChangedHandlerProperty, value);
}
static readonly DependencyProperty SelectionChangedHandlerProperty =
DependencyProperty.RegisterAttached("SelectionChangedHandler", typeof(SelectionChangedEventHandler), typeof(Attach), new UIPropertyMetadata(null));
//d is MultiSelector (d as ListBox not supported)
static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
if (GetSelectionChangedHandler(d) != null)
return;
if (d is MultiSelector)//DataGrid
{
MultiSelector multiselector = d as MultiSelector;
SelectionChangedEventHandler selectionchanged = null;
foreach (var selected in GetSelectedItems(d) as IList)
multiselector.SelectedItems.Add(selected);
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, multiselector.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
multiselector.SelectionChanged += GetSelectionChangedHandler(d);
}
else if (d is ListBox)
{
ListBox listbox = d as ListBox;
SelectionChangedEventHandler selectionchanged = null;
selectionchanged = (sender, e) =>
{
SetSelectedItems(d, listbox.SelectedItems);
};
SetSelectionChangedHandler(d, selectionchanged);
listbox.SelectionChanged += GetSelectionChangedHandler(d);
}}}
Usage in XAML:
<DataGrid ItemsSource="{Binding Path=SourceList}"
myControls:Attach.SelectedItems="{Binding Path=myMvvmSelectedItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionMode="Extended" />
You might be interested in the BookLibrary sample application of the WPF Application Framework (WAF). It shows how to synchronize the DataGrid.SelectedItems with the ViewModel. This might be very similar to SelectedCells.
Somewhere between perfect MVVM bindings and full event handler codebehinds there is the gray area of interactivity EventTriggers (see Blend SDK) :)
If you put an eventtrigger to the datagrid, and set to "SelectionChanged" and pass the eventargs to a command (use an EventToCommand actiontrigger) you could get the selected items from the eventargs hopefully...
Or use the multibinding as said in the MS thread :)

Logically combine dependency properties

I'm using C# 4.0 and have created a DependencyObject MyView.
In MyView, I have two DependencyProperties, PropA and PropB, both are booleans.
I want a third DependencyProperty, PropC, also a bool, and simply put, should always give me (PropA || PropB).
What is the best way to accomplish this?
I was also thinking of making PropC a readonly DependencyProperty, but have read about issues with binding to readonly dp's (WPF ReadOnly Dependency Properties using MVVM)
You can use the Dependency Property changed callback for PropA and PropB to set the value for PropC (don't use the CLR property wrapper for the Dependency Properties as they are never guaranteed to be called).
If you have these three DP's
public static readonly DependencyProperty PropAProperty =
DependencyProperty.Register("PropA",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false, PropAPropertyChanged));
public static readonly DependencyProperty PropBProperty =
DependencyProperty.Register("PropB",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false, PropBPropertyChanged));
public static readonly DependencyProperty PropCProperty =
DependencyProperty.Register("PropC",
typeof(bool),
typeof(MyView),
new PropertyMetadata(false));
public bool PropA
{
get { return (bool)this.GetValue(PropAProperty); }
set { this.SetValue(PropAProperty, value); }
}
public bool PropB
{
get { return (bool)this.GetValue(PropBProperty); }
set { this.SetValue(PropBProperty, value); }
}
public bool PropC
{
get { return (bool)this.GetValue(PropCProperty); }
set { this.SetValue(PropCProperty, value); }
}
you can use the property changed callback like this
private static void PropAPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyView myView = source as MyView;
myView.OnPropChanged();
}
private static void PropBPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
MyView myView = source as MyView;
myView.OnPropChanged();
}
public void OnPropChanged()
{
PropC = PropA || PropB;
}
This way, you'll always update the value of PropC everytime PropA or PropB changes
Also, PropC doesn't need to be a DP, it can be a normal CLR property if you implement INotifyPropertyChanged. Then the implementation can look like this instead
public void OnPropChanged()
{
OnPropertyChanged("PropC");
}
public bool PropC
{
get
{
return PropA || PropB;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
You could also bind PropC to PropA and PropB with a MultiBinding. Let me know if you want an example of this as well
The linked web page is for an unusual situation, a "push" binding. That is, a one-way-to-source binding was attempted on the read-only property, not on another property trying to bind to it. By contrast, if you want your property to be bindable to other properties using a one-way binding expression on the other property, then you can use a read-only dependency property without any issues.
Edit:
Here is an example:
<Grid>
<Grid.Resources>
<local:MyObject x:Key="myObject" PropertyA="True" PropertyB="False"/>
</Grid.Resources>
<StackPanel DataContext="{StaticResource myObject}">
<CheckBox IsChecked="{Binding PropertyA}" Content="PropertyA"/>
<CheckBox IsChecked="{Binding PropertyB}" Content="PropertyB"/>
<CheckBox IsChecked="{Binding PropertyC, Mode=OneWay}" IsEnabled="False" Content="PropertyC"/>
</StackPanel>
</Grid>
and the dependency properties, one of which is read-only:
public class MyObject : DependencyObject
{
public bool PropertyA
{
get { return (bool)GetValue(PropertyAProperty); }
set { SetValue(PropertyAProperty, value); }
}
public static readonly DependencyProperty PropertyAProperty =
DependencyProperty.Register("PropertyA", typeof(bool), typeof(MyObject), new UIPropertyMetadata(false, OnPropertyAOrBChanged));
public bool PropertyB
{
get { return (bool)GetValue(PropertyBProperty); }
set { SetValue(PropertyBProperty, value); }
}
public static readonly DependencyProperty PropertyBProperty =
DependencyProperty.Register("PropertyB", typeof(bool), typeof(MyObject), new UIPropertyMetadata(false, OnPropertyAOrBChanged));
public bool PropertyC
{
get { return (bool)GetValue(PropertyCProperty); }
set { SetValue(PropertyCPropertyKey, value); }
}
private static readonly DependencyPropertyKey PropertyCPropertyKey =
DependencyProperty.RegisterReadOnly("PropertyC", typeof(bool), typeof(MyObject), new UIPropertyMetadata());
public static readonly DependencyProperty PropertyCProperty = PropertyCPropertyKey.DependencyProperty;
private static void OnPropertyAOrBChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myObject = d as MyObject;
myObject.PropertyC = myObject.PropertyA || myObject.PropertyB;
}
}

WPF Binding Not Working

I am pretty sure I am doing something dreadfully wrong, but can't figure it out.
I created a simple wrapper around a class and added a dependency property so I could bind to it. However, the binding gives no errors, but does nothing.
In order to simplify things I changed the class to TextBox, and got the same results.
public class TextEditor : TextBox
{
#region Public Properties
#region EditorText
/// <summary>
/// Gets or sets the text of the editor
/// </summary>
public string EditorText
{
get
{
return (string)GetValue(EditorTextProperty);
}
set
{
//if (ValidateEditorText(value) == false) return;
if (EditorText != value)
{
SetValue(EditorTextProperty, value);
base.Text = value;
//if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("EditorText"));
}
}
}
public static readonly DependencyProperty EditorTextProperty =
DependencyProperty.Register("EditorText", typeof(string), typeof(TextEditor));
#endregion
#endregion
#region Constructors
public TextEditor()
{
//Attach to the text changed event
//TextChanged += new EventHandler(TextEditor_TextChanged);
}
#endregion
#region Event Handlers
private void TextEditor_TextChanged(object sender, EventArgs e)
{
EditorText = base.Text;
}
#endregion
}
When I run the following XAML the first gives results, but the second one (EditorText) doesn't even hit the EditorText property.
<local:TextEditor IsReadOnly="True" Text="{Binding Path=RuleValue, Mode=TwoWay}" WordWrap="True" />
<local:TextEditor IsReadOnly="True" EditorText="{Binding Path=RuleValue, Mode=TwoWay}" WordWrap="True" />
You're doing extra work in your CLR property. There is no guarantee that your CLR property will be used by WPF so you shouldn't be doing this. Instead, use metadata on your DP to achieve the same effect.
public string EditorText
{
get { return (string)GetValue(EditorTextProperty); }
set { SetValue(EditorTextProperty, value); }
}
public static readonly DependencyProperty EditorTextProperty =
DependencyProperty.Register(
"EditorText",
typeof(string),
typeof(TextEditor),
new FrameworkPropertyMetadata(OnEditorTextChanged));
private static void OnEditorTextChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textEditor = dependencyObject as TextEditor;
// do your extraneous work here
}

CheckBox Command Behaviors for Silverlight MVVM Pattern

I am trying to detect when an item is checked, and which item is checked in a ListBox using Silverlight 4 and the Prism framework. I found this example on creating behaviors, and tried to follow it but nothing is happening in the debugger. I have three questions:
Why isn't my command executing?
How do I determine which item was checked (i.e. pass a command parameter)?
How do I debug this? (i.e. where can I put break points to begin stepping into this)
Here is my code:
View:
<ListBox x:Name="MyListBox" ItemsSource="{Binding PanelItems, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Enabled}" my:Checked.Command="{Binding Check}" />
<TextBlock x:Name="DisplayName" Text="{Binding DisplayName}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel:
public MainPageViewModel()
{
_panelItems.Add( new PanelItem
{
Enabled = true,
DisplayName = "Test1"
} );
Check = new DelegateCommand<object>( itemChecked );
}
public void itemChecked( object o )
{
//do some stuff
}
public DelegateCommand<object> Check { get; set; }
Behavior Class
public class CheckedBehavior : CommandBehaviorBase<CheckBox>
{
public CheckedBehavior( CheckBox element )
: base( element )
{
element.Checked +=new RoutedEventHandler(element_Checked);
}
void element_Checked( object sender, RoutedEventArgs e )
{
base.ExecuteCommand();
}
}
Command Class
public static class Checked
{
public static ICommand GetCommand( DependencyObject obj )
{
return (ICommand) obj.GetValue( CommandProperty );
}
public static void SetCommand( DependencyObject obj, ICommand value )
{
obj.SetValue( CommandProperty, value );
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached( "Command", typeof( CheckBox ), typeof( Checked ), new
PropertyMetadata( OnSetCommandCallback ) );
public static readonly DependencyProperty CheckedCommandBehaviorProperty =
DependencyProperty.RegisterAttached( "CheckedCommandBehavior", typeof( CheckedBehavior ), typeof( Checked ), null );
private static void OnSetCommandCallback( DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e )
{
CheckBox element = dependencyObject as CheckBox;
if( element != null )
{
CheckedBehavior behavior = GetOrCreateBehavior( element );
behavior.Command = e.NewValue as ICommand;
}
}
private static CheckedBehavior GetOrCreateBehavior( CheckBox element )
{
CheckedBehavior behavior = element.GetValue( CheckedCommandBehaviorProperty ) as CheckedBehavior;
if( behavior == null )
{
behavior = new CheckedBehavior( element );
element.SetValue( CheckedCommandBehaviorProperty, behavior );
}
return behavior;
}
public static CheckedBehavior GetCheckCommandBehavior( DependencyObject obj )
{
return (CheckedBehavior) obj.GetValue( CheckedCommandBehaviorProperty );
}
public static void SetCheckCommandBehavior( DependencyObject obj, CheckedBehavior value )
{
obj.SetValue( CheckedCommandBehaviorProperty, value );
}
}
Your sample is not enough for a repro on my PC, but here are the things that I'd correct first:
The bindings in the DataTemplate are missing ", Mode=TwoWay" if you want the Enabled property to be set in your PanelItem
(- The ItemsSource binding does not need the Mode=TwoWay, but this is a minor detail)
The DataContext of the ItemTemplate is the PanelItem instance, so the binding of the Check command seems wrong: there is no Check property on PanelItem. The binding should be:
my:Checked.Command="{Binding ElementName=MyListBox, Path=DataContext.Check}
This kind of stuff is always hard to debug. Look at the output window of VS; binding errors (path not found) are displayed there. When you have a DP change callback (like OnSetCommandCallback), a breakpoint there will tell you how the binding went.
Edit: added after 1st comment (as I can't use the comment feature on the PC I have to use now)
The Command attached property is defined as type CheckBox in the Checked class, but the Check property in the VM is a DelegateCommand. I agree with WPF on the type mismatch :-)
The property declaration is like this:
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command", typeof( CheckBox ),
typeof( Checked ), new PropertyMetadata( OnSetCommandCallback ) );
The second parameter should be the property type, so I guess something like ICommand in your case.
Out of curiosity: in OnSetCommandCallback, you don't care for the value set to the Command property (which is in e.NewValue). How do you relate an instance of CheckedBehavior to the Check property of the VM ?
Edit after second comment:
No, the 2nd paragraph above is not related to your question. Maybe it does not make sense. I can't figure out the role of CheckedBehavior.
Concerning the question of which item is checked/unchecked: what do you need more precisely ? You have a PanelItem instance, whose Enabled property is being set to true or false through the biding; so the checked items are the ones with Enabled=true.
Edit after 3rd comment:
Thanks for the explanation of your needs. You're not really using the path parameter of the binding to the attached property, you could write:
my:Checked.Command="{Binding}"
This way, e.NewValue is the bound PanelItem in the OnSetCommandCallback. So it could be given to the CheckedBehavior instance (in its constructor), which could forward it when calling Execute of ICommand.
CheckBehavior.cs:
public class CheckBehavior : CommandBehaviorBase<CheckBox>
{
public CheckBehavior(CheckBox element) : base(element)
{
element.Checked += OnElementChecked;
element.Unchecked += OnElementChecked;
}
private void OnElementChecked(object sender, RoutedEventArgs e)
{
if (sender is CheckBox && ((CheckBox)sender).IsPressed)
{
base.ExecuteCommand();
}
}
}
CheckCommand.cs:
public class CheckCommand
{
public static ICommand GetCommand(DependencyObject obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static void SetCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
public static object GetCommandParameter(DependencyObject obj)
{
return obj.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(DependencyObject obj, object value)
{
obj.SetValue(CommandParameterProperty, value);
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CheckCommand), new PropertyMetadata(OnSetCommandCallback));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter", typeof(object), typeof(CheckCommand), new PropertyMetadata(OnSetCommandParameterCallback));
public static readonly DependencyProperty CheckedCommandBehaviorProperty =
DependencyProperty.RegisterAttached("CheckedCommandBehavior", typeof(CheckBehavior), typeof(CheckCommand), null);
private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
CheckBox element = dependencyObject as CheckBox;
if (element != null)
{
CheckBehavior behavior = GetOrCreateBehavior(element);
behavior.Command = e.NewValue as ICommand;
}
}
private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
CheckBox element = dependencyObject as CheckBox;
if (element != null)
{
CheckBehavior behavior = GetOrCreateBehavior(element);
behavior.CommandParameter = e.NewValue;
}
}
private static CheckBehavior GetOrCreateBehavior(CheckBox element)
{
CheckBehavior behavior = element.GetValue(CheckedCommandBehaviorProperty) as CheckBehavior;
if (behavior == null)
{
behavior = new CheckBehavior(element);
element.SetValue(CheckedCommandBehaviorProperty, behavior);
}
return behavior;
}
public static CheckBehavior GetCheckCommandBehavior(DependencyObject obj)
{
return (CheckBehavior)obj.GetValue(CheckedCommandBehaviorProperty);
}
public static void SetCheckCommandBehavior(DependencyObject obj, CheckBehavior value)
{
obj.SetValue(CheckedCommandBehaviorProperty, value);
}
}
And example:
<CheckBox Commands:CheckCommand.Command="{Binding MyCheckCommand}}"
Commands:CheckCommand.CommandParameter="{Binding}"/>

Resources