I have to bind my GUI to the interface:
public interface IMain
{
public CTopObject MyObject { get; }
public CInsideObject MidObj { get; }
public CInsideObject MyCollectionObject { get; }
}
Objects definitions:
public class CMain
{
public CTopObject MyObject { get { return this.myObject; }
public CInsideObject MidObj { get { return this.MyObject.SomeObject; } }
public CInsideObject MyCollectionObject
{ get
{
if(this.MyObject.Thedict.Contains(0))
return this.MyObject.TheDict[0];
else
return default(CInsideObject);
}
} //0 is ofcourse an example
}
public class CTopObject
{
private string someString;
public string SomeString
{
get { return this.someString; }
set
{
this.someString = value;
if(this.PropertyChanged!=null) this.PropertyChanged(this, "SomeString");
}
}
private ObservableDictionary int, CInsideObject> theDict; //how to open bracket on stackoverflow? ;)
public ObservableDictionary int, CInsideObject> TheDict
{
get { return this.theDict; }
}
private CInsideObject someObject;
public CInsideObject SomeObject
{
get { return this.someObject; }
}
//There is constructor. After that, there is init method,
//that creates new thread. The thread updates SomeString,
//representing object state. After thread raise specified event
//callback method begin to initialize SomeObject and
//after callback from SomeObject it adds some objects to
//TheDict, and initialize them.
}
public class CInsideObject : INotifyPropertyChanged
{
private string someString;
public string SomeString
{
get { return this.someString; }
set
{
this.someString = value;
if(this.PropertyChanged!=null) this.PropertyChanged(this, "SomeString");
}
}
//There is constructor, and init method that creates new thread.
//This thread updates SomeString that represents actual state of object.
}
Now in my application main.xaml.cs file, I have field
private IMain theMain;
Then I set DataContext of my window to IMain field:
theMain = new CMain(... some args ...);
this.DataContext = this.theMain;
...initialize theMain...
in XAML it looks like this:
Label Content="{Binding Path=MyObject.SomeString}" Name="label1"/>
Label Content="{Binding Path=MyMidObj.SomeString}" Name="label2"/>
Label Content="{Binding Path=MyCollectionObject.SomeString}" Name="label3"/>
Initialization like I wrote is mostly in other threads than main app thread. Still, in every "state object" there is SomeString property, which calls NotifyPropertyChanged event. SomeString is updated whenever thread change object state. Window GUI has some labels that represents SomeString of proper objects.
I don't know why only MyObject of IMain interface is updating binded label when SomeString of TopObject changes. Labels for MidObj and MyCollectionObject are empty somehow.
Sorry for my english, I hope my question is not to confusing ;)
Thanks
Joe
what happens if you set your binding to:
Label Content="{Binding Path=MyObject.SomeObject.SomeString}" Name="label2"/>
Label Content="{Binding Path=MyObject.TheDict}" Name="label3"/>
Related
I currently facing the issue that my DataGrid binding is not refreshing the UI.
My ViewModel and Object inherit from INotifyPropertyChanged.
Here is my code:
XAML:
<DataGrid Grid.Row="2" DataContext="{StaticResource MainViewModel}" ItemsSource="{Binding TestCollection, Mode=OneWay}" AutoGenerateColumns="True"/>
ViewModel:
public class MainViewModel: ViewModelBase
{
private ObservableCollection<ProductDisplayItem> _testCollection;
public ObservableCollection<ProductDisplayItem> TestCollection
{
get => _testCollection;
set => SetProperty(ref _testCollection, value);
}
private async void SendSearch()
{
//MyCode
.....
IEnumerable<ProductDisplayItem> displayItems = DisplayItemHelper.ConvertToDisplayItems(products);
TestCollection = new ObservableCollection<ProductDisplayItem>(displayItems);
}
}
My Object:
public class ProductDisplayItem: ViewModelBase
{
private string _mfrPartNumber;
private double _unitPrice;
private int _stock;
public string MfrPartNumber
{
get => _mfrPartNumber;
set => SetProperty(ref _mfrPartNumber, value);
}
public double UnitPrice
{
get => _unitPrice;
set => SetProperty(ref _unitPrice, value);
}
public int Stock
{
get => _stock;
set => SetProperty(ref _stock , value);
}
public ProductDisplayItem()
{
}
public ProductDisplayItem(string mfrp, double unitPrice, int stock)
{
MfrPartNumber = mfrp;
UnitPrice = unitPrice;
Stock = stock;
}
}
And my ViewModelBase:
public abstract class ViewModelBase: IDisposable, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = "")
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
OnPropertyChanged(propertyName);
return true;
}
public void Dispose()
{
}
}
I also tried to add the items to the ObservableCollection instead of creating a new one, but with the same result.
I hope anyone can help me with that.
Thanks in advance
The most common cause of such errors is confusion about ViewModel instances: UI elements are bound to one instance, and you are modifying a collection in another instance.
Since WPF MVVM usually provides for using the main ViewModel in only one instance, try using Singleton.
Fresh topic with a similar question: Is it a correct approach to create static viewModel in MVVM?
First implementation option from there:
1) If:
in general, in principle, under no circumstances is it assumed that a ViewModel can have several instances at the assembly level in which it is created;
if this does not create any security problems, since the static instance can be accessed by everyone;
if static values are sufficient to create a single instance. In most cases, this means that the ViewModel has only one non-parameterized constructor.
Then in this case it is worth using Singleton.
Example:
public class MainWindowViewModel : ViewModelBase
{
// The only instance available outside of this class.
public static MainWindowViewModel Instanse { get; }
= new MainWindowViewModel();
// All constructors must be MANDATORY HIDDEN.
private MainWindowViewModel()
{
// Some code
}
// Some code
}
To get this instance in XAML, x: Static is used.
You can get the entire instance, or create a binding to a separate property.
<SomeElement
DataContext="{x:Static vm:MainWindowViewModel.Instance}"/>
<SomeElement
Command="{Binding ButtonCommandEvent,
Source={x:Static vm:MainWindowViewModel.Instance}}"/>
Ok I figured it out. It's the DataContext...
Works fine after removing it from xaml.
I have a class that contains two other objects.
A variable in the first object bind to WPF element, call it X.
A similar variable in the other object.
I want that when the PropertyChanged event happens, it will change the variable in the second object.
Here is the code that does not work for me:
The class that contains the variables: (I had register to property changed event)
private Class1 _var1;
public Class1 Var1
{
get { return _var1; }
set
{
_var1= value;
if (_var1!= null)
_var1.PropertyChanged += new PropertyChangedEventHandler(_var1_PropertyChanged);
else
_var1.PropertyChanged -= new PropertyChangedEventHandler(_var1_PropertyChanged);
}
}
void _var1_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName=="X")
Var2.X= Var1.X;
}
private Class2 _var2;
public Class2 Var2
{
get { return _var2; }
set { _var2= value; }
}
Class 1:
private int _x;
public int X
{
get { return _x; }
set
{
if (_x!= value)
{
_x= value;
NotifyPropertyChanged("X");
}
}
}
class 2:
public int X { get; set; }
PropertyChanged work in class 1 but he did not come to an event I created in a class that contains the two variables, why?
I'm not sure I understand exactly what you mean, but if I had a class with 2 variables that I wanted to change together, I would try the following:
First, define some SetAndNotify method or you'll get a headache from the PropertyChanged events:
public void SetAndNotify<T>(ref T field, T value, Expression<Func<T>> exp)
{
if (!Equals(field, value))
{
field = value;
OnPropertyChanged(exp);
}
}
Add it to some base class that will handle this event.
Second, in your setter for Var1 you register for the change event and not set anything, is that on purpose?
Third and last, there's no problem with changing more than one property in a setter, but make sure it's the public property that you change:
private SomeType privateVar1;
public SomeType PublicVar1
{
get { return privateVar1; }
set
{
SetAndNotify(ref privateVar1, value, () => PublicVar1);
MyOtherPublicVar = someNewValue; // this will activate the property's setter.
}
}
I hope this helps. If not, please try to clarify your question.
I'm writing a wrapper for OPC communication to a controller, and I have created a component like follows;
public class OPCGroup : Component
{
(snipped)
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<OPCItem> Items
{
get { return mItems; }
set { mItems = value; }
}
}
Each item in the Items list above looks like this
public class OPCItem : MarshalByRefObject, IDisposable
{
private String mName;
private String mTag;
private object mValue;
public String Name
{
get { return mName; }
set { mName = value; }
}
public String Tag
{
get { return mTag; }
set { mTag = value; }
}
public object Value
{
get { return mValue; }
set { Change(value, true); }
}
public event OPCItemEvent Changed;
}
Now the problem is that the Changed event of the OPCItem is not changeable in design time, i can add events to the items in run time using
opcGroup1.Items[0].Changed += new OPCItemEvent(Item0Changed);
However, i would like to be able to bind the events in design time instead as it is easier.
I have tried creating the events manually using the IEventBindingService in a custom UITypeEditor as follows
IEventBindingService eventBindingService = provider.GetService(typeof(IEventBindingService)) as IEventBindingService;
EventDescriptorCollection edc = TypeDescriptor.GetEvents(Group.Items[0]);
EventDescriptor Event = edc.Find("Changed", false);
PropertyDescriptor pd = eventBindingService.GetEventProperty(Event);
pd.SetValue(Group.Items[0], Group.Items[0].Name + "Changed");
But then I get the error
Events cannot be set on the object passed to the event binding service because a site associated with the object could not be located
So then i implemented the IComponent interface in the OPCItem class to get the Site property, but now the collection items are shown on the designer.
Any advice on how to approach this problem?
I've defined a class with the following property:
private ObservableCollection<Job> allJobs;
Access is defined as follows:
public ObservableCollection<Job> AllJobs
{
get
{
return this.allJobs;
}
set
{
this.allJobs = value;
}
}
The get set works fine when I assign a whole ObservableCollection to the property, the set works fine for retrieving it obviously. But why have I lost all the methods that normally allow me to 'Add' (i.e. add 1 job to the collection)?
At the moment I'm having to create a temporary collection to populate to then assign to the allJobs property and I shouldn't have to.
Any ideas??
What do you mean with 'lost methods'? Have you tried AllJobs.Add()? The following code works for me:
void Main()
{
AllJobs = new ObservableCollection<Job>();
AllJobs.Add(new Job());
}
public class Job { }
private ObservableCollection<Job> allJobs;
public ObservableCollection<Job> AllJobs
{
get
{
return this.allJobs;
}
set
{
this.allJobs = value;
}
}
EDIT:
Based on your comment I've amended my code as follows but everything still works for me, I have noticed however that you don't seen to initialise the allJobs collection anywhere.
void Main()
{
PresentationManager.Instance.AllJobs.Add(new Job());
}
public class Job { }
sealed class PresentationManager
{
public static readonly PresentationManager Instance = new PresentationManager();
private PresentationManager()
{
allJobs = new ObservableCollection<Job>();
}
private ObservableCollection<Job> allJobs;
public ObservableCollection<Job> AllJobs
{
get { return this.allJobs; }
set { this.allJobs = value; }
}
}
Normally you wouldn't want a setter for such a property, as you would lose all events bound to the ObservableCollection when the setter is used.
public ObservableCollection<Job> AllJobs { get; private set; }
I'm sorta at a loss to why this doesn't work considering I got it from working code, just added a new level of code, but here's what I have. Basically, when I bind the ViewModel to a list, the binding picks up when Items are added to a collection. However, if an update occurs to the item that is bound, it doesn't get updated. Basically, I have an ObservableCollection that contains a custom class with a string value. When that string value gets updated I need it to update the List.
Right now, when I debug, the list item does get updated correctly, but the UI doesn't reflect the change. If I set the bound item to a member variable and null it out then reset it to the right collection it will work, but not desired behavior.
Here is a mockup of the code, hopefully someone can tell me where I am wrong. Also, I've tried implementing INofityPropertyChanged at every level in the code below.
public class Class1
{
public string ItemName;
}
public class Class2
{
private Class2 _items;
private Class2() //Singleton
{
_items = new ObservableCollection<Class1>();
}
public ObservableCollection<Class1> Items
{
get { return _items; }
internal set
{
_items = value;
}
}
}
public class Class3
{
private Class2 _Class2Instnace;
private Class3()
{
_Class2Instnace = Class2.Instance;
}
public ObservableCollection<Class1> Items2
{
get {return _Class2Instnace.Items; }
}
}
public class MyViewModel : INofityPropertyChanged
{
private Class3 _myClass3;
private MyViewModel()
{
_myClass3 = new Class3();
}
private BindingItems
{
get { return _myClass3.Items2; } // Binds when adding items but not when a Class1.ItemName gets updated.
}
}
The answer to your question is that Class1 needs to implement INotifyPropertyChanged.
public class Class1 : INotifyPropertyChanged
{
private string _ItemName;
public string ItemName
{
get { return _ItemName; }
set
{
_ItemName = value;
NotifyPropertyChanged("ItemName");
}
}
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public event PropertyChangedEventHandler PropertyChanged;
}