How to achieve INotifyPropertyChanged functionality for the values in a bool[]? - wpf

I have a bool array of size 4 and I want to bind each cell to a different control.
This bool array represents 4 statuses (false = failure, true = success).
This bool array is a propery with a class:
class foo : INotifyPropertyChanged {
...
private bool[] _Statuses;
public bool[] Statuses
{
get {return Statuses;}
set {
Statuses = value;
OnPropertyChanged("Statuses");
}
}
In XAML there are 4 controls, each one bound to one cell of the array:
... Text="{Binding Path=Statuses[0]}" ...
... Text="{Binding Path=Statuses[1]}" ...
... Text="{Binding Path=Statuses[2]}" ...
... Text="{Binding Path=Statuses[3]}" ...
The problem is that the notify event is raised only when I change the array itself and isn't raised when I change one value within the array, i.e, next code line raises the event:
Statuses = new bool[4];
but next line does not raises the event:
Statuses [0] = true;
How can I raise the event each time one cell is changed?

You need to expose your statuses as an indexer, then raise a property change event that indicates that the indexer has changed.
private bool[] _Statuses;
public bool this[int index]
{
get { return _Statuses[index]; }
set
{
_Statuses[index] = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(Binding.IndexerName));
}
}
See this blog post:
http://10rem.net/blog/2010/03/08/wpf---silverlight-quick-tip-inotifypropertychanged-for-indexer

It doesn't raise the event becuase Array itself doesn't implement INotifyPropertyChanged. You can either use a different container than the primitive array (anything that implements INotifyCollectionChanged liked ObservableCollection<T> should do) OR you have to call RaisePropertyChanged("Statuses") each time you update the Statuses array OR, as metioned in another answer, use one class that implement INotifyPropertyChanged that contains 4 properties.

You cannot do it while using an Array. Changing a value at any index on an Array does not raise change notification required by the UI.
Can you use a class with four properties that implements INotifyPropertyChanged interface instead?

Related

Enum comboBox lists every item twice

I have a custom ComboBox in WinForms that is supposed to display an enumeration. Basically, I derived from ComboBox and in the constructor, I load the items from the enum.
The problem is that, even though at the end of the process it says the count of items is the same as the enum item count, the visual representation shows as if the same items were added twice. In other words, if my enum had values Value1 and Value2 the ComboBox says it has two values but when dropping down it shows Value1, Value2, Value1, Value2 as if I had added them twice.
public class EnumComboBox : ComboBox
{
public EnumComboBox() : base()
{
foreach (MyEnum p in Enum.GetValues(typeof(MyEnum)) {
base.Items.Add(p);
}
SelectedIndex = 0;
}
}
As you can see in the code, in the constructor the items are added ONCE but they are listed twice. What am I doing wrong here?
Rather than in the Constructor the manner to get it to behave properly was to leave the constructor empty and move the insertion code to the OnCreate method:
protected override void OnCreateControl()
{
if (!DesignMode) {
foreach (MyEnum p in Enum.GetValues(typeof(MyEnum))) {
Items.Add(p);
}
SelectedIndex = 0;
}
}

Setting the Text property of a DropDownList ComboBox to an invalid value doesn't raise exception?

I'm looking at ways to resolve an issue with a Winforms application, which uses a ComboBox control. Specifically, the ComboBox (Style=DropDownList) is bound to a datasource and, as the user navigates through some other data, the "Text" property of the ComboBox property is set - and the user can select some other value.
The trouble starts when the value I set the "Text" property to is not in the list of available items. It seems that nothing happens. Take the following simple example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
myComboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
//myComboBox1.Items.AddRange(new[] { "One", "Two", "Three" });
List<KeyValuePair<Int32, String>> values = new List<KeyValuePair<Int32, String>>();
values.Add(new KeyValuePair<Int32, String>(1, "One"));
values.Add(new KeyValuePair<Int32, String>(2, "Two"));
values.Add(new KeyValuePair<Int32, String>(3, "Three"));
myComboBox1.DataSource = values;
myComboBox1.ValueMember = "Key";
myComboBox1.DisplayMember = "Value";
button1.Click += (s, e) => { myComboBox1.Text = "Four"; };
button2.Click += (s, e) => { myComboBox1.SelectedIndex -= 1; };
}
}
public class MyComboBox : System.Windows.Forms.ComboBox
{
public override string Text
{
get { return base.Text; }
set { MessageBox.Show(value); base.Text = value; }
}
}
This technique is used throughout a large application, so when it was noticed that (in the example above) setting the "Text" to "Four" does nothing, I thought that maybe I could trap this and throw an exception. In reality, the application is peppered with code like this:
if (myDataRow.IsBlahNull())
myComboBox1.Text = "";
else
myComboBox1.Text = myDataRow.Blah;
Now, while I appreciate that setting "SelectedIndex = -1" would be better for the "IsNull" case, the fact remains that myDataRow.Blah may not be a valid value. Also, the application is written (and live) so the fewer changes the better.
So, my immediate thought was "let's override the Text property setter and check that the value is in the list". That, it turns out, is nothing like as simple as it would seem. The problem being that the "Text" property is set to all kinds of things, in all kinds of scenarios. For example, it's set when the DataSource property is assigned, or when the SelectedIndex is set to -1. Also, it's set to the string representation of the selected item - so if you happen to have a ComboBox control that's bound to a List of KeyValue pairs, you get the "Text" property set to something like "[Key,Value]". If it's bound to a DataTable/DataView, you get the string representation of the DataRow, and that gets even harder to detect.
It's at this point I thought that there might be another way to achieve the desired result (which is to detect the setting of the Text property to some invalid value - which does nothing).
Any ideas ?
Upon reflection, is this a reasonable work-around ?
/// <summary>
/// Gets or sets the text associated with this control.
/// </summary>
public override string Text
{
get { return base.Text; }
set
{
base.Text = value;
if ((value != null) && (base.Text != value))
if (value == "")
this.SelectedIndex = -1;
else
throw new ArgumentException(String.Format("Cannot set Text property of {0} to \"{1}\".", this.Name, value));
}
}

DataBinding with dependency property in wpf

I have 2 windows w1 and w2 with one textbox each and data in txtbox1 in w1 needs to be appearing in w2 txtbox .How to achieve this using dependency properties
I suggest you rather use a common DataContext for your two windows than binding them to each other. As an example, let's assume you have this class as DataModel:
public class MyDataModel : INotifyPropertyChanged
{
private string text;
public string Text {
get { return text; }
set {
text = value;
RaisePropertyChanged("Text");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can set it as DataContext on your windows:
MyDataModel model = new MyDataModel();
model.Text = "Hello World";
MyWindow a = new MyWindow();
MyOtherWindow b = new MyOtherWindow();
a.DataContext = model;
b.DataContext = model;
If you have done this, you can set the Text Property of the TextBoxes in every window to a Binding like this:
<TextBox Text="{Binding Text}"/>
Both Textboxes will now automatically update if you set model.Text to another value.
Is there a mother of the 2 forms? a form that creates both of them?
In this case what I normally do, since I'm implementing INotifyOnPropertyChanged anyway, is in the mother I subscribe to the w1's PropertyChanged event, get the value, and then place it in the property of w2.
I wouldnt really use Dependency Properties
I know its not pretty but I generally prefer it because I like to keep the 1 viewmodel to 1 view relationship. My boss thinks its easier to read and understand this way.

WPF ComboBox binding behaviour

I have the following XAML markup:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
SelectedValuePath="ProductNumber" />
My View's DataContext is bound to a viewmodel containing a public property called SelectedCustomer. Customer objects contain a FavouriteProduct property of type Product and Product objects contain public properties ProductNumber and ProductName.
The behaviour I'm looking for is to have the SelectedItem of the ComboBox update the Text in the TextBox and vice versa. ComboBox to TextBox works just fine. Selecting any product in the ComboBox updates the TextBox with the product number of that product. However when I try to go the other way I get som strange behaviour. It only works for the items that come before the selected item. I will try to explain:
Consider the following list of products ([Product Number], [Product Name]):
Fanta
Pepsi
Coca Cola
Sprite
Water
Now lets say that the SelectedCustomer's favourite product is Coca Cola (must be a developer). So when the window opens the TextBox reads 3 and the ComboBox reads Coca Cola. Lovely. Now lets change the product number in the TextBox to 2. The ComboBox updates it's value to Pepsi. Now try to change the product number in the TextBox to anything higher then the number for Coca Cola (3). Not so lovely. Selecting either 4 (Sprite) or 5 (Water) makes the ComboBox revert back to Coca Cola. So the behaviour seems to be that anything below the item that you open the window width from the list in the ItemSource does not work. Set it to 1 (Fanta) and none of the others work. Set it to 5 (Water) and they all work. Could this have to do with some initialisation for the ComboBox? Potential bug? Curious if anyone else have seen this behaviour.
UPDATE:
After reading Mike Brown's response I have created properties for SelectedProduct and SelectedProductNumber. The problem I am having with this is that as soon as you select something from the ComboBox you end up in an endless loop where the properties keep updatign each other. Have I implemented the OnPropertyChanged handler incorrectly or is there something I am missing? Here is a snippet of code from my ViewModel:
private int _SelectedProductNumber = -1;
public int SelectedProductNumber
{
get
{
if (_SelectedProductNumber == -1 && SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
OnPropertyChanged("SelectedProductNumber");
_SelectedProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (_SelectedProduct == null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
OnPropertyChanged("SelectedProduct");
_SelectedProductNumber = value.ProductNumber;
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
UPDATE 2
I have changed the implementation slightly now by updating the SelectedCustomer.FavouriteProduct from both properties and then using that when reading their values. This now works but I'm not sure it's the 'correct way'.
private int _SelectedProductNumber = 0;
public int SelectedProductNumber
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProductNumber = SelectedCustomer.Product.ProductNumber;
return _SelectedProductNumber;
}
set
{
_SelectedProductNumber = value;
SelectedCustomer.FavouriteProduct = ProductList.FirstOrDefault(s => s.ProductNumber == value);
OnPropertyChanged("SelectedProductNumber");
OnPropertyChanged("SelectedProduct");
}
}
private Product _SelectedProduct;
public Product SelectedProduct
{
get
{
if (SelectedCustomer.Product != null)
_SelectedProduct = SelectedCustomer.Product;
return _SelectedProduct;
}
set
{
_SelectedProduct = value;
SelectedCustomer.FavouriteProduct = value;
OnPropertyChanged("SelectedProduct");
OnPropertyChanged("SelectedProductNumber");
}
}
Your aim is not too clear so I have written the folloiwng so support either options I can see.
To keep two elements bound to one item in sync you can set the IsSynchronizedWithCurrentItem="True" on your combobox as shown below:
<TextBox x:Name="MyTextBox" Text="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Name="MyComboBox" ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"
SelectedValue="{Binding Path=SelectedCustomer.FavouriteProduct.ProductNumber}"
IsSynchronizedWithCurrentItem="True"
SelectedValuePath="ProductNumber" />
This will mean everything in the current window bound to the same background object will keep in sync and not give the odd behaviours you are seeing.
This quote form this longer MSDN article describes the effect:
The IsSynchronizedWithCurrentItem
attribute is important in that, when
the selection changes, that is what
changes the "current item" as far as
the window is concerned. This tells
the WPF engine that this object is
going to be used to change the current
item. Without this attribute, the
current item in the DataContext won't
change, and therefore your text boxes
will assume that it is still on the
first item in the list.
Then setting the Mode=TwoWay as suggested by the other answer will only ensure that both when you update the textbox the underlying object will be updated and when you update the object the textbox is updated.
This makes the textbox edit the selected items text and not select the item in the combolist with the matching text (which is the alternative think you are may be trying to achieve?)
To achieve the synchronised selection effect it may be worth setting IsEditable="True" on the combobox to allow users to type items in and dropping the text box. Alternatively if you need two boxes replace the textbox with a second combobox with IsSynchronizedWithCurrentItem="True" and IsEditable="True" then a styled to make it like a text box.
What you want to do is expose separate properties on your ViewModel for the currently selected product and currently selected product number. When the selected product is changed, update the product number and vice versa. So your viewmodel should look something like this
public class MyViewModel:INotifyPropertyChanged
{
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
_SelectedProduct = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProduct"));
_SelectedProductID = _SelectedProduct.ID;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
}
}
private int _SelectedProductID;
public int SelectedProductID
{
get { return _SelectedProductID; }
set
{
_SelectedProductID = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedProductID"));
_SelectedProduct = _AvailableProducts.FirstOrDefault(p => p.ID == value);
PropertyChanged(this,new PropertyChangedEventArgs("SelectedProduct"));
}
}
private IEnumerable<Product> _AvailableProducts = GetAvailableProducts();
private static IEnumerable<Product> GetAvailableProducts()
{
return new List<Product>
{
new Product{ID=1, ProductName = "Coke"},
new Product{ID = 2, ProductName="Sprite"},
new Product{ID = 3, ProductName = "Vault"},
new Product{ID=4, ProductName = "Barq's"}
};
}
public IEnumerable<Product> AvailableProducts
{
get { return _AvailableProducts; }
}
private Customer _SelectedCustomer;
public Customer SelectedCustomer
{
get { return _SelectedCustomer; }
set
{
_SelectedCustomer = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCustomer"));
SelectedProduct = value.FavoriteProduct;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So now your XAML binds to the appropriate properties and the viewModel is responsible for syncrhronization
<TextBox
x:Name="MyTextBox"
Text="{Binding Path=SelectedProductID, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox
x:Name="MyComboBox"
ItemsSource="{Binding AvailableProducts}"
DisplayMemberPath="ProductName"
SelectedItem="{Binding SelectedProduct}" />
Don't forget to implement the rest of INotifyPropertyChanged and the GetAvailableProducts function. Also there may be some errors. I hand typed this here instead of using VS but you should get the general idea.
Try:
SelectedItem="{Binding Path=YourPath, Mode=TwoWay"}
instead of setting SelectedValue and SelectedValuePath.
Might work with SelectedValue too, don't forget the Mode=TwoWay, since this isn't the default.
A good approuch would to use the master detail pattern - bind the master (the items view, e.g. combobox) to the data source collection and the detail view (e.g. text box) to the selected item in the source collection, using a binding converter to read/write the appropriate property.
Here is an example:
http://blogs.microsoft.co.il/blogs/tomershamam/archive/2008/03/28/63397.aspx
Notice the master binding is of the form {Binding} or {Binding SourceCollection} and the details binding is of the form {Binding } or {Binding SourceCollection}.
To get this working you need to wrap you collection with an object that keeps the selected item. WPF has one of these built-in: ObjectDataProvider.
Example:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/068977c9-95a8-4b4a-9d38-b0cc36d06446

WPF Create generic "Selector Tool" for multiple classes

the problem is pretty easy if you reduce it to one class. Given the following image, I want to create a simple two sided control, which puts items from one list into the other depending on a boolean value.
EDIT: You can of course click on items in both lists, and the item switches to the other list. Also, a callback is called, in case I need to update some DB stuff...
I created a nice picture to movivate you a little bit, because I am stuck...
The world is not so simple like the example: How would you solve this for various classes.
Imagine a class like "Car" with "IsFast". Or a class like "Fruits" with "ILikeIt". I do not want to reprogramm the WPF control each time, I need some way to bind... (oh, I think I just got an idea)... but still, is there any good practice how to allow generic classes (like T) as long as they implement certain properties.. Or a wrapper class?
I have no idea, how would you solve it. Simple binding with OnClick Functions seems not enough... Not sure... And, by the way, "write 3 controls" IS a suitable answer. If it is simpler, just tell me.
alt text http://img31.imageshack.us/img31/96/listexample.png
I think I understand what you are after, this should get you started.
I am assuming that your usercontrol has two listviews, one intended for the true items named "TrueList", the other for the false items named "FalseList".
Extend your usercontrol from ItemsCollection and bind the ItemsSource property of each listview to the ItemsSource of the parent usercontrol.
Add a TrueFilter and a FalseFilter property to your usercontrol:
Predicate<object> trueFilter;
public Predicate<object> TrueFilter
{
get
{
return trueFilter;
}
set
{
if (trueFilter!= null && this.TrueList.Items != null)
this.TrueList.Items.Filter -= trueFilter;
trueFilter = value;
if (trueFilter!= null && this.TrueList.Items != null)
this.TrueList.Items.Filter += trueFilter;
}
}
Predicate<object> falseFilter;
public Predicate<object> FalseFilter
{
get
{
return falseFilter;
}
set
{
if (falseFilter!= null && this.FalseList.Items != null)
this.FalseList.Items.Filter -= falseFilter;
filter = value;
if (falseFilter!= null && this.FalseList.Items != null)
this.FalseList.Items.Filter += falseFilter;
}
}
Then create an "IToggle" (or some other more meaningful name) interface:
public interface IToggle
{
Predicate<object> TrueFilter { get; }
Predicate<object> FalseFilter { get; }
}
Then, extend ObservableCollection for each of your custom classes, implementing the "IToggle" interface:
public class Cars : ObservableCollection<Car>, IToggle
{
Predicate<object> trueFilter;
public Predicate<object> TrueFilter
{
get
{
if (trueFilter == null)
trueFilter = new Predicate<object>(this.TrueFilterPredicate);
return trueFilter;
}
}
private bool TrueFilterPredicate(object value)
{
Car car = (Car)value;
return car.IsFast;
}
Predicate<object> falseFilter;
public Predicate<object> FalseFilter
{
get
{
if (falseFilter == null)
falseFilter = new Predicate<object>(this.FalseFilterPredicate);
return falseFilter;
}
}
private bool FalseFilterPredicate(object value)
{
Car car = (Car)value;
return !car.IsFast;
}
Next, override the ItemsSource property on your user control:
public new IEnumerable ItemsSource
{
get { return base.ItemsSource; }
set
{
if (value != null && !(value is IToggle))
throw new Exception("You may only bind this control to collections that implement IToggle.");
base.ItemsSource = value;
this.TrueFilter = base.ItemsSource == null ? null : (base.ItemsSource as IToggle).TrueFilter;
this.FalseFilter = base.ItemsSource == null ? null : (base.ItemsSource as IToggle).FalseFilter;
}
}
Finally, call TrueList.Items.Refresh() and FalseList.Items.Refresh() on your event callbacks to refresh the item views whenever you switch an item from true to false, and vice versa.
This solution still requires writing some implementation code for each custom class (the true and false filters), but it should keep the extra code to a minimum.
Alternatively, it would be a much simpler solution if you gave each of your custom classes a common interface, something like:
public interface Valid
{
bool IsValid { get; set; }
}
Then you could use a single set of filters (or style setters, or databinding with converters) to work against the "Valid" interface. Instead of "Car.IsFast" and "Fruit.ILikeIt" you would use "Car.IsValid" and "Fruit.IsValid".
How would I do it?
Create a control called PickList that subclasses ItemsControl and includes commands for picking a single item, picking all items, unpicking all items etcetera
Create a class called PickListItem that has an IsPicked property
Define the control template for PickList to include two ListBoxes and a bunch of buttons for picking one, all etcetera. The template would include a couple of CollectionViewSources to segregate those items that are picked (which would be on the right) from those that are not (which would be on the left)
You would then use this control just like any other ItemsControl, and reuse it for any data type you might have:
<PickList ItemsSource="{Binding People}">
<PickList.ItemContainerStyle>
<Style TargetType="{x:Type PickListItem}">
<Setter Property="IsPicked" Value="{Binding IsRich}"/>
</Style>
</PickList.ItemContainerStyle>
</PickList>
make a user control. add a SourceList dep. Property. Add a delegate property for your callback, add a delegate property for your "is in list" filter
use two ListBox controls ( a left and a right ), use CollectionViewSource to set the source of the two lists, using the filter delegate to determine membership.

Resources