I am using a custom ConfigurationElementCollection, which Implements INotifyCollectionChanged. Each of the elements that can be stored inside the collection (all of which inherit ConfigurationElement), also Implements INotifyPropertyChanged.
I have a config handler class that stores the collection in a property (CustomCollection), when its constructor is called. This ConfigHandler also Implements INotifyPropertyChanged.
The constructor is as follows:
_config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)
CustomCollection = (DirectCast(_config.GetSection("CustomCollection"), CustomConfigurationSection)).CustomCollection
I can bind to CustomCollection just fine:
<DataGrid DataContext="{Binding Source={x:Static l:Handlers.ConfigHandler}}" ItemsSource="{Binding Path=CustomCollection}" />
And this displays all the elements onscreen correctly.
As soon as I try to edit one of the elements, however, I get an exception:
System.InvalidOperationException: 'EditItem' is not allowed for this view.
at System.Windows.Controls.ItemCollection.System.ComponentModel.IEditableCollectionView.EditItem(Object item)
at System.Windows.Controls.DataGrid.EditRowItem(Object rowItem)
...
How can I make this binding editable? The only way I can think to do it, is to make some more traditional classes (i.e. not configuration classes) and copy all the data into those - but that seems like a waste when I've already got all the observable properties I need set up.
Is your collection an IEnumerable<ConfigurationElement>? Enumerables cannot be edited in the DataGrid. Convert it to a List<ConfigurationElement>
ConfigurationElementCollection seems to be an IEnumerable. From msdn:
public abstract class ConfigurationElementCollection : ConfigurationElement,
ICollection, IEnumerable
You should make your class a List:
CustomCollection = (from ConfigurationElement ce in someConfigurationElementCollection select ce).ToList();
Related
I have a viewModel with properties like the following and a set of specific attributes used throughout the viewmodels.
public class MyViewModel : BaseModel
{
[StringLength(50), Required]
[SetLockedForExistingEntities]
public string FirstName { get ... set ... }
public bool IsInNewMode { get; }
}
Now I want to apply such metaData in a view in a consistent way. Like... If bound, set TextBox maxlength from the MaxLengthAttribute. If SetLockedForExistingEntitiesAttribute is set, disable the control in case viewModel is not in some 'New' Mode etc..
Is that doable/a good idea to do with a custom MarkupExtension that replaces "Binding" for VM Bindings? Or would it be better to use a Behavior (applied via attached property) which tries to apply anything it can from the bound ViewModel property to the control it is attached to?
Usage would be like
(A) Attached dependencyproperty that reads the binding from TextBox.Text and applies behaviors
<TextBox Text="{Binding Model.FirstName, ValidatesOnDataErrors=True}" "bb:MyBindingHelper.ApplyViewModelBehaviors="True" />
(B) Custom MarkupExtension that does all in one
<TextBox Text="{BindingWithModelBasedBehaviors Model.FirstName}" />
You could write a markup extension that gets the property from the datacontext and reads attributes.
That would be kind of complicated but you can get the property name of properties where the source changed event was raised.
That looks rather like validation to me.
You could implement inotifydataerrorinfo in a base viewmodel and write code there that validates properties using attributes.
That's how the code in this works:
https://gallery.technet.microsoft.com/scriptcenter/WPF-Entity-Framework-MVVM-78cdc204
That works by the view telling the viewmodel which property's value just passed to the viewmodel.
You can extend the method you use for raising property changed to pass the property name to the validation.
Or you could even do the check from a method called in the property setter before you set the value on a property and not set the value if the new one fails validation.
As a specific property fails validation in a particular way you could run an action.
The production code version of that app I linked also has a dictionary of predicates used as well as attributes. They could have code in them references and sets other viewmodel properties.
I'm new to WPF. I need to Bind UI's ListBox to the source that is deep in Model Layer.
App scheme is on picture below. Desc:
My MainWindowViewModel Class has a Scheduler Property (Scheduler Class in Model layer).
Scheduler Class has a CurrentParser Property (Parser Class in Model layer).
Parser Class has a Result field (ParserResultMetaData Class in Model layer).
ParserResultMetaData Class has a Log field (Log is a List(Of String))
Log can be changed only programmatically from model layer (Parser adds lines during it's work).
So my question is how can I bind my ListBox to this List to match MVVM pattern.
As I get it now, ViewModel must have an ObservableCollection(Of String) witch is a copy of my List(Of String) from Model layer.
Somehow you need to notify the UI when a line is added to the collection. There are multiple ways to achieve this, but if the collection is modified from within the model layer, you need a mechanism for communicating this to other layers in one way or another.
Use an ObservableCollection in your Model layer.
While types like ObservableCollection and INotifyPropertyChanged are widely used in MVVM architectures, they are not specific to them and in some cases it can make sense to use them in the model layer directly. Using an ObservableCollection in your Parser class is one way to provide this notification mechanism. You can then bind the ItemsSource of the ListBox to Scheduler.Parser.Result.Log directly and it will update accordingly.
Create a wrapper property in your ViewModel.
If you don't want to use an ObservableCollection in your model, you can expose the List via a property in your ViewModel, for example:
public IEnumerable<string> ParserLog
{
get { return Scheduler.Parser.Result.Log; }
}
Then you need to manually notify the UI when an item is added, so you're gonna need an event (or something equivalent) which tells your ViewModel that the list changed and it needs to raise the PropertyChanged Event for the ParserLog property. Add code like this in your ViewModel:
this.Scheduler.Parser.ResultUpdated += (s, e) => this.RaisePropertyChanged("ParserLog");
This will tell the ListBox to update the items from the ParserLog property.
My datamodel is like this:
public class ModelA
{
public int ModelId{get;set;}
}
public class ModelB
{
public IEnumerable<ModelA> ChildObjects{get;set;}
}
Now in the Xaml, am using a DataGrid with the ItemSource as List(), and have a template column which binds to ChildObjects with a converter doing the job of getting the first element from ChildObjects and returning the value as that object's ModelId. Now all works fine till now. The issue is when I do sorting on this templated column.
I know one workaround is to have an extra property in ModelB which does the job of what converter is doing and make the sortmemberpath in xaml as that new property name, but that is not what I want as its against the model.
Is there any other perfect way to handle this scenario, as the SortMemberPath can't be made as expression as its just a contant.
You've tagged this MVVM, which I assume means your models are actually view models (or are at least wrapped by view models). That being the case, why wouldn't you add the extra property? After all, it's there to support the view. Your view needs the extra property, so your view model should provide it.
I have a BlogStore class which contains two observablecollections like so
public class BlogStore {
public ObservableCollection<Blog> blogs ...
public ObservableCollection<Blog> favourites ...
}
public BlogStore blogStore ...
no I want to reuse a control which does the following binding
ItemsSource="{Binding blogStore.blogs}
so that I can switch to favourites, the following does not work, but I would like something in a similar vein.
ItemsSource={Binding blogStore{Binding category, ElementName=blogControl}
and in the controls code behind i would have a dependency property.
maybe a converter could do the trick?
If you treat BlogStore as a ViewModel then it would expose a couple of other properties.
Category to which you bind what ever control you are using to choose the category to display.
Also a CategoryBlogs property which returns either the value of blogs or favourites depending on the value of Category.
You would be implementing INotifyPropertyChanged so you would ensure that a PropertyChanged event is fired for "CategoryBlogs" when the Category property is changed.
You would be binding ItemsSource just to CategoryBlogs.
Working with the MVVM pattern, I have a pair of view model classes that represent a two-tier data hierarchy, each with a corresponding UserControl that represents its view. Both view model classes implement INotifyPropertyChanged and the root level view model exposes a property that is relevant to both its own view and the child view.
The root level view acquires the root level view model as its data context and explicitly assigns a data context to its contained view. However, it also needs to bind one of the properties of the child view to the above-mentioned shared property. Here is how I have attempted to achieve this, but it's not working:
<UserControl x:Name="rootView">
<StackPanel>
<!-- other controls here -->
<my:ChildView
DataContext="{Binding Path=SelectedChild}"
EditingMode="{Binding ElementName=rootView, Path=DataContext.EditingMode />
</StackPanel>
</UserControl>
Although there are no runtime binding errors and the child view correctly binds to the appropriate child view model instance, its EditingMode property is never set. I have run tests to verify that the corresponding view model property is being modified and that it is notifying this change via INotifyPropertyChanged, but the binding fails to detect it.
Is there a better way to declare this binding or have I made a more basic architectural error?
Many thanks for your advice,
Tim
Update: As requested, I am posting some code to show a very simplified version of my views and view models, together with the results of an experiment that I have conducted that may provide some additional clues.
// The relevant parts of the ParentViewModel class
public class ParentViewModel : INotifyPropertyChanged
{
// Although not shown, the following properties
// correctly participate in INotifyPropertyChanged
public ChildViewModel SelectedChild { get; private set; }
public ContentEditingMode EditingMode { get; private set; }
}
// The relevant parts of the ChildViewModel class
public class ChildViewModel : INotifyPropertyChanged
{
// No properties of ChildViewModel affect this issue.
}
// The relevant parts of the ParentView class
public partial class ParentView : UserControl
{
// No properties of ParentView affect this issue.
}
// The relevant members of the ChildView class
public partial class ChildView : UserControl
{
public static readonly DependencyProperty EditingModeProperty =
DependencyProperty.Register(
"EditingMode",
typeof(ContentEditingMode),
typeof(PostView)
);
public ContentEditingMode EditingMode
{
get { return (ContentEditingMode)GetValue(EditingModeProperty); }
set { SetValue(EditingModeProperty, value); }
}
}
// The enumeration used for the EditingMode property
public enum ContentEditingMode
{
Html,
WYSYWIG
}
My intention is that the DataContext of the parent view instance will be assigned an instance of ParentViewModel and it will, in turn, assign the value of its SelectedChild property to the DataContext of the nested ChildView. All of this seems to work correctly, but the problem arises because the binding between ParentViewModel.EditingMode and ChildView.EditingMode does not work.
In an attempt to test whether there is a problem with my binding expression, I introduced a TextBlock adjacent to the ChildView and bound it similarly to the ParentViewModel.EditingMode property:
<UserControl x:Name="rootView">
<StackPanel>
<!-- other controls here -->
<TextBlock Text="{Binding ElementName=rootView, Path=DataContext.EditingMode}" />
<my:ChildView
DataContext="{Binding Path=SelectedChild}"
EditingMode="{Binding ElementName=rootView, Path=DataContext.EditingMode />
</StackPanel>
</UserControl>
In this test, the TextBlock is correctly updated every time the source property changes. However, if I set a breakpoint on the setter of ChildView.EditingMode, it never gets hit.
I'm baffled !
The simplest way to fix this is in your view model. Implement an EditingMode property in the child view model and bind to it. That way, you don't have to make any kind of guesses about what the right way to establish the binding might be; also, it's something that you can test outside of the UI.
Edit
Actually the right solution is not quite as simple, but it's worth knowing how to do.
What you want is for EditingMode in the child control to efficiently inherit its value from the parent control. Does that sound like something that anything else in WPF does? Like just about every framework element that implements dependency properties?
Implement EditingMode as a dependency property in both the parent and child UserControls and use property value inheritance, as described here. That takes the inheritance behavior out of the view model entirely and puts it where it belongs.
See if you can just use a full path to get the editing mode of the selected child:
<my:childView
DataContext="{Binding SelectedChild}"
EditingMode="{Binding SelectedChild.EditingMode />