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.
Related
I have a DataGrid showing some databases having quite some columns.
I would like that, when the user edit a new row, some values are set automatically.
With the windows form DataGrid that would be easy, since there's RowsAdded event handler.
But how could i handle this with the wpf DataGrid ??
Edit : my DataGrid is bound in Xaml to a public property which is an ITable. When user select a table in a ComboBox, the property is updated with corresponding table.
Yes there's autogenerating column, and the way the user can enter a new row is to edit the last blank row (default behaviour).
You can do this in the LoadingRow event. Try something like this:
private void myDataGrid_LoadingRow(object sender, System.Windows.Controls.DataGridRowEventArgs e)
{
MyObject myObject = e.Row.Item as MyObject;
if (myObject != null)
{
myObject.PropertyOne = "test";
myObject.PropertyTwo = 2;
}
}
Ok i think i got it.
When a DataTable is bound to a DataGrid, a CollectionView is created in order to see it. You can get it by using the (static/shared) CollectionView.GetDefaultView(ThePropertyThatIsBound) method.
Since it implements ICollectionChanged, you can add an event handler to the CollectionChangedEvent.
In the CollectionChanged event handler, if you have a new item (e.NewItems.Count>0) you must check it against System.Windows.Data.CollectionView.NewItemPlaceholder and if it is not a place holder, then it is a brand new item, for wich i can set all default values.
Assign a CollectionViewSource to your DataGrid then listen to the CollectionChanged event as following :
..
public CollectionViewSource ViewSource { get; set; }
..
this.ViewSource = new CollectionViewSource();
this.ViewSource.Source = new List<YourObjectType>();
this.ViewSource.View.CollectionChanged += View_CollectionChanged;
..
private void View_CollectionChanged(object sender,System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (e.NewItems.Count > 0)
{
YourObjectType myObject = e.NewItems[e.NewItems.Count-1] as YourObjectType;
if (myObject != null)
{
myObject.Property = TheValueYouWant;
..
}
}
}
..
<DataGrid ItemsSource="{Binding ViewSource.View}" ../>
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?
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
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.
I've been using WinForms databinding to display data from a database mapped with Fluent NHibernate, and that's been working great.
For example, I can just set a DataGridView's DataSource property from an entity's IList property, and voila - there's all the data!
But now I need to start adding and saving new data rows, and that's not going so well. I thought I'd be able to just enable the grid's AllowUserToAddRows property, and new rows would get added to the underlying IList in the entity, but that didn't work.
Then, after a little searching, I tried setting the DataSource property to a BindingList that was populated from the IList, but that's not being updated with new rows either.
During the course of my searches, I also came upon a few people reporting difficulty with WinForms and DataBinding in general, which makes me wonder if I should pursue that approach any further.
Is the DataBinding approach worth continuing? If so, can anyone suggest where I'm going wrong?
Or is it better to just handle all the DataGridView events associated with adding a new row, and writing my own code to add new objects to the IList property in my entity?
Other suggestions? (though I don't think switching to WPF is going to be an option, no matter how much better the databinding may be)
Can you load (or copy) your nHibernate entities into a generic List? If so, I have had good success in with two-way binding using a DataGridView bound to a generic List.
The key points are:
The generic list contains list objects where each is an instance of your custom class.
Your custom class must implement public properties for each of the fields to bind. Public fields didn't work for me.
Use a BindingSource to wrap the actual generic list.
The BindingSOurce allows you to set the AllowNew property to true. Binding directly to the List almost works, but the DataGridVieww does not display the "New row" line, even if AllowUsersToAddRows = true.
For example, add this code to a Form with a dataGridView1:
private List<MyObject> m_data = new List<MyObject>();
private BindingSource m_bs =new BindingSource();
private void Form1_Load(object sender, EventArgs e)
{
m_data.Add(new MyObject(0,"One",DateTime.Now));
m_data.Add(new MyObject(1, "Two", DateTime.Now));
m_data.Add(new MyObject(2, "Three", DateTime.Now));
m_bs.DataSource = m_data;
m_bs.AllowNew = true;
dataGridView1.DataSource = m_bs;
dataGridView1.AutoGenerateColumns = true;
dataGridView1.AllowUserToAddRows = true;
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
for (int i = 0; i < m_data.Count ; i++)
{
Console.WriteLine(string.Format("{0} {1} {2}", m_data[i].ID, m_data[i].Name, m_data[i].DOB));
}
}
}
public class MyObject
{
// Default ctor, required for adding new rows in DataGridView
public MyObject()
{
}
public MyObject(int id, string name, DateTime dob)
{
ID = id;
Name = name;
DOB = dob;
}
private int m_id;
public int ID
{
get
{
return m_id;
}
set
{
m_id = value;
}
}
private string m_name;
public string Name
{
get
{
return m_name;
}
set
{
m_name = value;
}
}
private DateTime m_dob;
public DateTime DOB
{
get
{
return m_dob;
}
set
{
m_dob = value;
}
}
}
When the form closes, the contents of the bound List are printed to the Output window.