I have a WPF application (.NET 4.5) using MEF and MVVMLight. There is a shared MEF Export called "DeviceSupervisor" which holds an ObservableCollection of a custom class, DeviceConfiguration, which isnt much more than a few string properties.
In the main app, you can add and remove devices to/from this collection. One of these modules displays the list of devices in a ComboBox for choosing.
Explicit Conditions:
Have a number of items added to the collection via the main app
Select one of the items from the drop down in the module
Remove the item that happens to be selected from the collection via the main app
Code to sync user changes to DeviceSupervisor:
foreach (DeviceComplete set in Devices)
{
set.Configuration.CommunicationDetails = set.Device.Value.CommunicationDetails;
if (DeviceSupervisor.AvailableDevices.Any(d => d.ID == set.Configuration.ID))
{
DeviceSupervisor.AvailableDevices
.Single(d => d.ID == set.Configuration.ID)
.CommunicationDetails = set.Configuration.CommunicationDetails;
}
else
{
DeviceSupervisor.AvailableDevices.Add(set.Configuration);
}
}
var missing = new HashSet<DeviceConfiguration>(DeviceSupervisor.AvailableDevices
.Except(Devices.Select(d => d.Configuration)));
foreach (DeviceConfiguration toRemove in missing)
{
// --- TargetException thrown here ---
DeviceSupervisor.AvailableDevices.Remove(toRemove);
}
CollectionViewSource in XAML on the module:
<CollectionViewSource x:Key="AvailableDevicesCvs"
Source="{Binding Path=AvailableDevices, Mode=OneWay}" Filter="AvailableDevicesCVS_Filter">
</CollectionViewSource>
ComboBox:
<ComboBox HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top" Width="120"
ItemsSource="{Binding Source={StaticResource AvailableDevicesCvs}}"
SelectedItem="{Binding Path=SelectedDevice}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=CommunicationDetails}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Exception:
An unhandled exception of type 'System.Reflection.TargetException' occurred in mscorlib.dll
Additional information: Object does not match target type.
The stack trace is almost all WindowsBase and PresentationFramework references that are grayed out. Intellitrace only shows the exception being thrown on the line that I commented above.
My Findings / Other Notes
Note that this exception is not thrown if the ComboBox has another item selected than the removed. Throwing explicit one-way bindings everywhere had no affect. The exception is thrown before any breakpoints I can throw in CollectionChanged or Filter events.
After a lot of struggling, reorganizing, and trial and error, I found that if I simply remove the ItemTemplate from the combobox, then everything works as expected - no exceptions and the ComboBox adjusts the selection to compensate for the missing item.
I'm nervous that I'm caught in some tunnel vision trap and can't see the silly mistake I made - but more nervous that its something else.
I've since traced the issue to the CommunicationDetails property. The library with the source file for that class did not have a reference to MVVMLight, and as a quick shortcut to get a xyzChanged even (for other uses) I wrote the property like the following:
Old
private string mCommunicationDetails;
public string CommunicationDetails
{
get
{
return mCommunicationDetails;
}
set
{
bool changed = (mCommunicationDetails != value);
mCommunicationDetails = value;
if (changed)
{
CommunicationDetailsChanged.Raise(this, EventArgs.Empty);
}
}
}
public event EventHandler CommunicationDetailsChanged;
Replacing it with a vanilla {get;set;} property works. I've since replaced it with an MVVMLight property
New
private string mCommunicationDetails;
public string CommunicationDetails
{
get
{
return mCommunicationDetails;
}
set
{
if (Set(() => CommunicationDetails, ref mCommunicationDetails, value))
{
CommunicationDetailsChanged.Raise(this, EventArgs.Empty);
}
}
}
public event EventHandler CommunicationDetailsChanged;
And it works.
If anyone cares to take the time to find or explain the real source of this error I'll accept that as the answer, but I'm accepting this for now to close the issue.
Related
I am building a WPF app and figured I would use AutoMapper to make copies of objects in my viewmodels. The problem I am having is that appears that AutoMapper is attempting to use a bound control as a source value and I don't understand why. I am new to AutoMapper so I figure I am missing some detail.
Details
UI has a list box and three buttons (Add, Edit, Delete). If nothing is selected in the list box, then only the Add button is active.
If an item is selected in the list box then all buttons are active.
If the Add button is clicked, a new empty object is created with properties bound to text boxes in the UI.
If the Edit button is clicked, a copy of the item selected in the list box is made and the copy's properties are bound to the text boxes in the UI.
All of this works. The problem occurs if I try to use AutoMapper to make the copy.
Here is the code in question. I have included what works (doing the copy manually, property by property) and the code that fails when using AutoMapper.
There are three properties in the viewmodel involved:
// Bound to the list box's ItemsSource property
public ObservableCollection<CarType> CarTypes
{
get { return _carTypes; }
set { SetProperty(ref _carTypes, value); }
}
// Bound to the list box's SelectedIndex property
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
SetProperty(ref _selectedIndex, value);
}
}
// Properties of CarTypeDto are bound to text boxes in the Edit UI
public CarType CarTypeDto
{
get
{
// When the value is retrieved, a copy of the selected item
// is made and the copy is returned.
if (SelectedIndex != -1)
{
// What works==========
// CarType newDto = new CarType();
// CarType src = CarTypes[SelectedIndex];
// newDto.Id = src.Id;
// newDto.Name = src.Name;
// newDto.Description = src.Description;
// _carTypeDto = newDto;
// What does NOT work============
CarType src = CarTypes[SelectedIndex];
CarType newDto = _mapper.Map<CarType>(src); <=== fails here
_carTypeDto = newDto;
// Incorrect Solution #1 ======================
// Changed the above two lines to this solves the problem:
_mapper.Map<CarType, CarType>(src,_carTypeDto);
}
return _carTypeDto;
}
set
{
SetProperty(ref _carTypeDto, value);
}
}
When I try to use AutoMapper it gets to the line that fails and the debugger shows this error:
System.Windows.Data Error: 17 : Cannot get 'CarTypeDto' value (type 'CarType') from '' (type 'CarTypesViewModel'). BindingExpression:Path=CarTypeDto.Description; DataItem='CarTypesViewModel' (HashCode=66939890); target element is 'TextBox' (Name='DescriptionTextBox'); target property is 'Text' (type 'String')
What is confusing me is that I get a reference to the selected item (src) and attempt to map that to a new instance of CarType (newDto). Neither of these items (src or newDto) is part of the binding to the DescriptionTextBox in the editing UI. Why is the binding becoming an issue when I use AutoMaper?
The commented code (manually copying properties) works fine.
In case it helps, here are the bindings in question:
<ListBox x:Name="ContentView"
DockPanel.Dock="Top"
ItemsSource="{Binding CarTypes}"
Background="{StaticResource ControlBackgroundBrush}"
Foreground="{StaticResource ControlForegroundBrush}"
BorderBrush="{StaticResource ControlForegroundBrush}"
SelectedIndex ="{Binding SelectedIndex}" >
<TextBox Grid.Row="1"
Grid.Column="0"
Name="NameTextBox"
Margin="5"
Width="100"
MaxLength="10"
Text="{Binding CarTypeDto.Name, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"
Name="DescriptionTextBox"
Margin="5"
Width="300"
MaxLength="30"
Text="{Binding CarTypeDto.Description,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
In the bootstrap portion of the app I initialize the mapper like so...
private void InitializeAutomapper()
{
var mapperConfig = new MapperConfiguration(cfg =>
{
cfg.CreateMap<CarType, CarType>();
});
_mapper = new Mapper(mapperConfig);
}
...and then inject _mapper into the viewmodels as needed. I have tested the mapper and it will correctly map objects. The issue shows up when I attempt to do the mapping in the CarTypeDto property shown above.
==============================
EDIT - Note about the solution
I wanted to add a note about the solution for folks who may read this later - especially folks newer to AutoMapper and property binding (like myself).
The answer below made me realize that my approach to populating the CarTypeDto property was not correct. In the final solution I revamped the property to this:
public CarType CarTypeDto
{
get { return _carTypeDto; }
set { SetProperty(ref _carTypeDto, value); }
}
Then, I update the value to the CarTypeDto property elsewhere when needed. For example when the Edit button is clicked, I then retrieve the selected value and map it to CarTypeDto. The code shown at the top of this post is not the appropriate way to populate the value for a bound property.
While all of this had nothing to do with AutoMapper, when I tried to use AutoMapper it brought the larger problem to light.
The error mentioned by you is a binding error not the Automapper exception message.
System.Windows.Data Error: 17 : Cannot get 'CarTypeDto' value (type 'CarType') from '' (type 'CarTypesViewModel'). BindingExpression:Path=CarTypeDto.Description; DataItem='CarTypesViewModel' (HashCode=66939890); target element is 'TextBox' (Name='DescriptionTextBox'); target property is 'Text' (type 'String')
Now about mapping is not working ---- Before using Automapper, the map should be created so that AutoMapper can understand how to map one object/type to another. In your scenario, since you need to type from same type then you should create a map like
_mapper.CreateMap<MyType, MyType>();
The CreateMap should be called in Automapper profile initialization.
Update on your binding error:
The binding error is appearing as you are trying to create a new Object of CarType everytime in Get property so CarTypeDto property is not pointing to the object which was bound initially at XAML. Instead of creating a new object by mapper you should work on same object and update the property.
I've created an example to illustrate my problem.
ViewModel:
public class VM : INotifyPropertyChanged
{
private double _value = 1;
public double Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged();
}
}
public VM()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromTicks(1);
timer.Tick += (s, e) => { Value += 1; };
timer.Start();
}
// OnPropertyChanged stuff ...
}
}
View:
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<TextBox Text="{Binding Value, IsAsync=True, FallbackValue=Test}"/>
</Grid>
When running my application the text in the textbox flickers. During the update process the FallbackValue is displayed, which makes no sense to me.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed? Is there a way to display the old Value during an async update process?
This seems normal to me, given that you are using IsAsync=True in your binding. From the documentation:
While waiting for the value to arrive, the binding reports the FallbackValue, if one is available
When the PropertyChanged event is raised, WPF initiates the process of updating the target of the binding. Normally this would happen synchronously, with the property getter called immediately to update the value.
But you are using IsAysnc=True, so instead WPF fills in the target with the fallback value, and starts an asynchronous request to retrieve the actual property value later. Until that request has completed, the fallback value is displayed.
Does anyone knows the purposes or what are the benefits that during the update process the FallbackValue is displayed?
Per the documentation, the intent behind the IsAsync=True setting is that it's used when the property getter is, or could be, slow. Your code has told WPF that the property value has changed, so it knows the old value is no longer valid. Your code has also told (via the IsAsync in the XAML) that the property getter could take some time to provide the new value, so it defers retrieving that value until later.
In the meantime, what should WPF display? That's what the fallback value is there for.
Is there a way to display the old Value during an async update process?
If you don't want the behavior that is designed for this feature in WPF, you should just retrieve the new data asynchronously yourself, and update the property via the setter when you have it. It's not a good idea for a property getter to be slow anyway, so this would be a better design in any case.
I had the same problem but with an image source. I've removed the IsAsync on the binding and I have made my getter async:
// This field hold a copy of the thumbnail.
BitmapImage thumbnail;
// Boolean to avoid loading the image multiple times.
bool loadThumbnailInProgress;
// I'm using object as the type of the property in order to be able to return
// UnsetValue.
public object Thumbnail
{
get {
if (thumbnail != null) return thumbnail;
if (!loadThumbnailInProgress) {
// Using BeginInvoke() allow to load the image asynchronously.
dispatcher.BeginInvoke(new Action(() => {
thumbnail = LoadThumbnail();
RaisePropertyChanged(nameof(Thumbnail));
}));
loadThumbnailInProgress = true;
}
// Returning UnsetValue tells WPF to use the fallback value.
return DependencyProperty.UnsetValue;
}
}
Sometimes a binding will fail, failure is important to consider. Fallback value option presents users a message if an error occurs, rather than nothing happening. If you would like your fallbackvalue to display the previous value contained, I could think of a few ways of trying : possibly saving the value in a reference string and/or to another control, then binding to that control
But if you don't want the fallbackvalue displayed at all, you need to do a code inspection to see how your binding is failing/or is slow, and contain it in your code behind
I've found an approach to avoid flickering by just inheriting from textbox and overriding it's textproperty-metadata.
Custom TextBoxControl
public class CustomTextBox : TextBox
{
static CustomTextBox()
{
TextProperty.OverrideMetadata(typeof(CustomTextBox), new FrameworkPropertyMetadata(null, null, CoerceChanged));
}
private static object CoerceChanged(DependencyObject d, object basevalue)
{
var tb = d as TextBox;
if (basevalue == null)
{
return tb.Text;
}
return basevalue;
}
}
View
<Window.DataContext>
<namespace:VM/>
</Window.DataContext>
<Grid>
<namespace:CustomTextBox Text="{Binding Value, IsAsync=True}"/>
</Grid>
It's important to have the text-binding without a fallbackvalue. So during update process the text is set to the textproperty defalut value - so in this case to null.
The CoerceChanged handler checks whether the new value is null. If it's so he returns the old value so that during update process there is still the old value displayed.
I am using ComboBox in wpf as below and want to update ComboBox behind the seen if i update collection :-
<xmlns:dataProvider="clr-namespace:DataProvider"
<UserControl.Resources>
<dataProvider:BackOfficeDataProvider x:Key="DataProvider"/>
</UserControl.Resources>
<ComboBox x:Name="groupGroupNameCombo" HorizontalAlignment="Left" Margin="368,123,0,0" VerticalAlignment="Top" Width="226" Height="31" SelectionChanged="groupGroupNameCombo_SelectionChanged" DisplayMemberPath="GroupName" SelectedItem="{Binding ParentID, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ItemsSource="{Binding GroupParentList, Mode=TwoWay, NotifyOnTargetUpdated=True, NotifyOnSourceUpdated=True, Source={StaticResource DataProvider}}" IsSynchronizedWithCurrentItem="True">
</ComboBox>
Class BackOfficeDataProvider {
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get { return groupParentList ; }
set
{
groupParentList = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("GroupParentList");
}
}
public void loadComboListData();
{
GroupParentList = (ObservableCollection<Categories>) //fetching data from database using NHibernate directly getting list ;
}
}
my front end class which has refresh button :-
private void RefreshButton_Click(object sender, RoutedEventArgs e)
{
new BackOfficeDataProvider().loadComboListData();
}
when application load that time i can see the item in combobox but when i click on Refresh button that time it load updated data from database but not updating combobox untill i use below code
groupGroupNameCombo.ItemsSource = null;
groupGroupNameCombo.ItemSource = GroupParentList ;
Its a manually thing i have to do always to refresh combobox, how can i make it automatic like if i update collection then it should update combobox at the same time and i don't need to use above workaround.
I think this may have something to do with breaking the coupling between the combobox and the ObservableCollection when doing this:
GroupParentList = //fetching data from database;
Try this instead:
var dbCategories = // Get data from DB
GroupParentList.Clear();
foreach (var item in dbData)
GroupParentList.Add(item);
The point is to update the items in the collection, not the collection itself.
Also, try defining your collection like this, it should'nt have to be instantiated more than once (i.e no setter):
public static ObservableCollection<Categories> groupParentList = null;
public virtual ObservableCollection<Categories> GroupParentList
{
get
{
if (groupParentList == null)
groupParentList = new ObservableCollection<Categories>();
return groupParentList;
}
}
Hogler is right, your approach of assigning a new ObservableCollection object to the binding property will break how binding works. For ObservableCollection to work, you will need to modify the items in the collection itself, ObservableCollection is responsible of publishing list changes to the binding target. When you assign a new collection to the binding target, the list will not get refresh unless you published PropertyChanged event again to register this new binding source.
In your later comment you did state that you only instantiate ObservableCollection once only, which is not obvious from your posted code. It appears to me that the reason why it doesn't work is because you assign a new collection to the "GroupParentList" each time you run "loadComboListData".
Try this ..
once You are done getting data from your database in groupParentList , Add below Line, it will work as below :-
GroupParentList = new ObservableCollection<Categories>(groupParentList )
I'm using wpf, mvvmlight and EF. I have two properties on my entity object which are loosely linked and in my XAML, I would like to have one change when the other changes. Right now, I'm having a problem with the first property changed event firing twice.
My xaml setup is like this, I have DataGrid up top on my xaml, I have my
ItemsSource="{Binding MonthlyDonorDetails}" IsSynchronizedWithCurrentItem="True"
The textbox is like,
<TextBox Grid.Column="0" Text="{Binding BankCustomerID}"/>
Next, in my VM, I load up the data like this,
int rowID = 1;
foreach(var row in monthlyDonorsQuery)
{
row.RowID = rowID++;
row.PropertyChanged += new PropertyChangedEventHandler(MonthlyDonorDataRow_PropertyChanged);
}
MonthlyDonorDetails = new ObservableCollection<MonthlyDonorFundCode>(monthlyDonorsQuery);
This affords me a row id on each row to help user differentiate, and (I'm thinking) allows me to setup a handler for when individual fields change within the row. Then I set the backing collection for the grid. And this is all working just fine.
NOW, I want to be able to keep two fields in lock-step with each other, an AccountID and an CustomerID. When the user manually\typing\input changes the AccountID fields, I want to have some code to change the CustomerID, but I don't want this firing needlessly multiple times. Right now its firing twice, and I don't know why? Can anyone see my mistake please?
private void MonthlyDonorDataRow_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "FirstName" || e.PropertyName == "LastName")
RaisePropertyChanged("DonorName");
if(e.PropertyName == "AccountID")
{
MonthlyDonorFundCode monthlyDonation = sender as MonthlyDonorFundCode;
int customerID = GetCimsIdFromBankCustomerID(monthlyDonation.AccountID);
Debug.WriteLine("should be setting to " + customerID);
}
}
When I'm debugging this, all I can see is the EF setter is getting called twice, but I don't know why cause I'm not setting that value. Any help\guidance would be appreciated. Thank you very much.
I think I found out my problem. But it raises another problem for me. I'm calling the method which adds a PropertyChanged handler, twice, while loading. That's another problem I have to figure out.
Thank you Lavr for trying to help me out.
Put breakpoint in AccountID setter and look at stack trace then setter is called. You can find "who" updating your property twice.
Also you can update AccountID setter like this:
private int _accountId;
public int AccountID
{
get { return this._accountId; }
set
{
if (this._accountId == value) return;
this._accountId = value;
this.RisePropertyChanged("AccountID");
}
}
I have the same problem with twice invocation of propertychanged. The same thing when I try to catch the extra invoke of the propertychanged, the call stack says it is called from the [External Code]. After I upgraded my Xamarin version from 4.0 to latest (Xamarin 5.0).
I know I am missing something here and I could use a pointer. Within a project I have an expander control when this control is clicked it makes a RIA call to a POCO within my project to retreive a second set of data. I am using the SimpleMVVM toolkit here so please let me know if I need to expand on any additional areas.
Within the xaml the expander is laid out as
<toolkit:Expander Header="Name" Style="{StaticResource DetailExpanderSytle}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="Expanded">
<ei:CallMethodAction
TargetObject="{Binding Source={StaticResource vm}}"
MethodName="showWarrantNameDetail"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<StackPanel Orientation="Vertical">
<sdk:DataGrid AutoGenerateColumns="true" ItemsSource="{Binding NameResult}" AlternatingRowBackground="Gainsboro" HorizontalAlignment="Stretch" MaxHeight="200">
</sdk:DataGrid>
<local:NameContainer DataContext="{Binding}" />
</StackPanel>
</toolkit:Expander>
I am using the expression Dll coupled with Simple MVVM to get at the methods in the view model vs commands.
Within the view model I have the following code
public void showWarrantNameDetail()
{
//set flags
IsBusy = true;
CanDo = false;
EntityQuery<WarrantNameDataView> query = App.cdContext.GetWarrantNameDataViewsQuery().Where(a => a.PrimaryObjectId == Convert.ToInt32(RecID));
Action<LoadOperation<WarrantNameDataView>> completeProcessing = delegate(LoadOperation<WarrantNameDataView> loadOp)
{
if (!loadOp.HasError)
{
processWarrantNames(loadOp.Entities);
}
else
{
Exception error = loadOp.Error;
}
};
LoadOperation<WarrantNameDataView> loadOperation = App.cdContext.Load(query, completeProcessing, false);
}
private void processWarrantNames(IEnumerable<WarrantNameDataView> entities)
{
ObservableCollection<WarrantNameDataView> NameResult = new ObservableCollection<WarrantNameDataView>(entities);
//we're done
IsBusy = false;
CanDo = true;
}
When I set a break on the processWarrantName I can see the NameResult is set to X number of returns. However within the view the datagrid does not get populated with anything?
Can anyone help me understand what I need to do with the bindings to get the gridview to populate? Other areas of the form which are bound to other collections show data so I know I have the data context of the view set correctly. I've tried both Data context as well as Items Source and no return?
When I set a break on the code the collection is returned as follows so I can see that data is being returned. Any suggestions on what I am missing I would greatly appreciate it.
With regards to the page datacontext I am setting it in the code behind as follows:
var WarrantDetailViewModel = ((ViewModelLocator)App.Current.Resources["Locator"]).WarrantDetailViewModel;
this.DataContext = WarrantDetailViewModel;
this.Resources.Add("vm", WarrantDetailViewModel);
Thanks in advance for any suggestions.
Make ObservableCollection<WarrantNameDataView> NameResult a public property of your ViewModel class. Your view will not be able to bind to something that has a private method scope (or public method scope, or private member scope).
//declaration
public ObservableCollection<WarrantNameDataView> NameResult { get; set }
//in the ViewModel constructor do this
NameResult = new ObservableCollection<WarrantNameDataView>();
//then replace the original line in your method with:
//EDIT: ObservableCollection has no AddRange. Either loop through
//entities and add them to the collection or see OP's answer.
//NameResult.AddRange(entities);
If processWarrantNames gets called more than once, you might need to call NameResult.Clear() before calling AddRange() adding to the collection.
Phil was correct in setting the property to public. One note I'll add is there is no AddRange property in SL or ObservableCollection class that I could find. I was able to assign the entities to the OC using the following code
private ObservableCollection<WarrantNameDataView> warrantNameResult;
public ObservableCollection<WarrantNameDataView> WarrantNameResult
{
get { return warrantNameResult; }
set
{
warrantNameResult = value;
NotifyPropertyChanged(vm => vm.WarrantNameResult);
}
}
and then within the return method
WarrantNameResult = new ObservableCollection<WarrantNameDataView>(entities);
This worked and passed to the UI the collection of data.