How to apply a Converter to each item of an ObservableCollection? - c#

I am using C# and .NET 4.5 to create a MVVM desktop application. I have a set of view model entries that are already contained in an ObservableCollection<MyEntryClass>.
I need to use a third-party control to display the data. This control requires that the entries be converted to their own entry classes. They provide an example on how to do that, using XAML and an IValueConverter. It boils down to ...Items="{Binding Path=The.Source, Converter={StaticResource CustomDataConverter}}... and a Converter that is implemented roughly like this:
public class CustomDataConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
IEnumerable<CustomItem> dataContext = value as IEnumerable<CustomItem>;
ObservableCollection<OutputItem> items = new ObservableCollection<OutputItem>();
foreach (CustomItem customItem in dataContext)
{
OutputItemitem =
new OutputItem
{
// ... some value transfers ...
};
items.Add(item);
}
return items;
}
While this works for the initialization of the control, it breaks the "observation chain" between the control and the ObservableCollection specified in the binding -- simply because the custom converter creates its own list of items. Is there a way to change the binding instructions so that the converter is not called once for the entire source collection, but once for every single collection element? If not, what alternative strategies exist for dealing with this kind of situation?
(In the example above I have left out some code that registers an event handler to the source object's PropertyChanged event and updates the target object. Updates of individual item properties are working correctly, it's just the updates of the list that are not handled.)
EDIT: The third party control in question is the GanttChartDataGrid from the Gantt Chart Light Library.

You could try to use the converter in the ItemTemplate, if this third party control offers such possibility. With standard WPF controls it should go something like this:
<ListBox ItemsSource="{Binding Path=The.Source}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Converter={StaticResource CustomDataConverter}}"
ContentTemplate="{StaticResource MyOptionalDataTemplate}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But maybe the easiest way to achieve this would be to bind a collection that contains OutputItems...

Another approach could be to forget to use converters in XAML code and do your conversions in the ViewModel.
The converter you posted havent a ConvertBack method so I'm guessing the list of ObservableCollection<OutputItem> needed by your thirdy parts control is readonly, meaning no modifications will occur from XAML to C# - as everybody supposes using a Grafic Chart library.
So you can have 2 properties, one private containing your custom object you will update in the ViewModel basing upon your business logic, then one public property binded to your thirdy parts UserControl:
private ObservableCollection<OutputItem> myPrivateData
{
get;
set;
}
private ObservableCollection<CustomItem> DataToPlotOnGant
{
get;
set;
}
Then you can do your conversion stuff inside CollectionChanged event of your private collection:
this.myPrivateData.CollectionChanged += (s, e) =>
{
// do convertion stuff here
};
and choose to update DataToPlotOnGant list by modifying just the item changed in the private collection - more efficiently - or to create every time a new collection like the converter did.

Related

How to dynamically create content (labels/grids) from Object method that returns List<string>

I have tried to search google to get the answer but I seem not to be able to ask the question the right way so none of answers describe my problem.
The problem is: I want to create unknown number of controls (grids, textboxes, label {pick one} - it doesn't matter for this example. I'll just edit it for my purpose later) based on result from Object method that returns List<string>. Let's say the method returns List with 4 items so on startup of the application I want to see four Labels/Textboxes (in rows) with the text from the list.
I'm learning with WPF so I did some tutorials etc and I am able to bind values from object to single Label/Textbox/Control in general but have no idea how to do that dynamically with whole result of List items.
I just can't even imagine that in head so its hard to change it into code.
Let's say I have the following object:
namespace Test
{
public class Robots
{
public List<string> GetAllRobots()
{
List<string> resultList = new List<string>();
resultList.add["Robot1"];
resultList.add["Robot2"];
resultList.add["Robot3"];
resultList.add["Robot4"];
return resultList;
}
}
}
This is the part I have no clue how to build to generate/bind Four separate labels into XAML.
namespace Test
{
/// <summary>
/// Interaction logic for UCRobots.xaml
/// </summary>
public partial class UCRobots : UserControl
{
public UCRobots()
{
InitializeComponent();
List<string> dataList = new Robots().GetAllRobots();
this.DataContext = dataList;
}
}
}
Is there any tutorial how to write the XAML part you could point me to? Or anyone willing to help me in answers?
You can display the content of your collection with any type of ItemsControl. Some examples are ListBox, TreeView, HeaderedItemsControl, TabControl, and even ItemsControl itself. These controls take a list of items and display them according to the ItemTemplate.
In your example, you would edit the UCRobots.xaml file to have the following
<!-- The ItemsSource property defines the list of items.
Here we are binding directly to the DataContext of the UCRobots class.
You could also bind to a property of the object that is set as the DataContext -->
<ItemsControl ItemsSource="{Binding}">
<!--
The below is commented out because the default DataTemplate for the ItemTemplate
property is to display a TextBlock that binds directly to the item
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
The Text property is binding directly to the item in the list.
If your list contained objects with properties, you could bind to one of those properties.
For example, if your list contained a list of People objects, you could bind the
Text property to the Name property of your class
</DataTemplate>
</ItemsControl.ItemTemplate>
-->
</ItemsControl>

Display a List<T> by using x:Bind?

I'm currently trying to figure out how to use the x:Bind stuff in UWP/xaml.
I'm learning more everyday and the app I'm curently writing is getting way easier to manage since I can implement the MVVM patterns now.
But there is one thing I encountered now...
I have a List<T> which I want to display in my UI. How can I bin this list while using x:Bind?
Or do I have to convert it into something else first?
Best Regards,
Daniel
You can certainly use a List<T> for binding, but usually ObservableCollection<T> is preferable, because it also allows the UI to observe list changes, as opposed to List<T> that will not update after bound first. You can create an ObservableCollection from List using the constructor:
ObservableCollection<T> data = new ObservableCollection<T>( list );
In any case, you first have to create a property (but fields are also supported with x:Bind) in your view model:
public ObservableCollection<T> Data { get; } = new ObservableCollection<T>();
Remember that binding connects to the instance, so if you would set a new instance to Data property, it the binding would not update. For that to work you need to implement INotifyPropertyChanged interface on your view mdoel and raise PropertyChanged event in the setter.
Now, to display the items in your UI you need a list control like ListView or GridView and bind it to your collection:
<ListView ItemsSource="{x:Bind Data, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate>
<!-- your template -->
<TextBlock Text="{Binding SomePropertyOfT}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can find a thorough walk through data binding in documentation as well.
If your desire is to have a collection which might change over time, and you want the UI to be notified of such modifications, you should not utilize List<T>!
Use instead ObservableCollection<T>, which implements the INotifyPropertyChanged and INotifyCollectionChanged which is the heart of the MVVM, allowing the ViewModel/View to communicate between each other.
Such collection automatically handles the addiction/removal of elements automatically for you!
Anyway, here is a simple example, showing how you can use a List to communicate with your View.
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
RandomList = new List<string>() {
"random 1", "random 2", "random 3"
};
}
List<string> RandomList { get; set; }
MainPage.xaml:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ComboBox ItemsSource="{x:Bind RandomList}"
PlaceholderText="List of Random things"/>
</Grid>
Here we defined a {x:Bind } to populate the ItemsSource's dependency property, which accepts a Collection.
This Binding is defined with the default binding Mode, which is OneTime for compiled bindings. For instance, if you were to perform Bindings with {Binding } markup, the default mode is OneWay;
Since you created a Collection with List rather than ObservableCollection, there would be no reason to specify any other mode than the default, since you haven't implement a mechanism for your CLR collection to actually notify the View of an update.

Windows Phone 8.1 Listview value mapping

I have a Listview control in a XAML page which has it's ItemSource bound to a collection in the code behind.
The bound data model within the collection that is bound to each ListView item has one integer field which can contain the value 0 or 1.
I am binding this field to a TextBlock in the ItemTemplate of the ListView, and so in the ListView I am seeing rows containing the text 0 or 1.
My goal is to see "sometext1" instead of 0 and "sometext2" instead of 1 in the ListView without changing the ItemSource itself.
The problems I am seeing are:
TextBlock controls seems to unable to override from code to create a custom TextBlock in which I can change values programatically.
If I use TextBox control instead, I can change the values, but the program slows down when large amount of data is shown and it also not shown the changed values. (In debug mode I can see, that text property has the new value, but the TextBox is empty on screen.)
This is where developing for the Windows platform really is easiest if you do things in the "Windows" way. This involves taking advantage of the awesome functionality built into the XAML presentation framework like binding and converters!
I'm going to ignore that the data comes from a database, as it could come over the network or created programmatically and it shouldn't matter, bottom line is it exists in memory somewhere.
Assuming you have a model class for your data:
class Data
{
public int intField { get; set; }
public string otherField { get; set; }
}
and in the code behind of your page with the listview, an collection of data objects:
private ObservableCollection<Data> ListViewData = new ObservableCollection<Data>();
We can then bind your ListView data to this collection in the XAML page as I'm assuming you have done:
<ListView ItemsSource="{Binding ListViewData}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tbIntField" Text="{Binding intField, Mode=OneWay}"/>
<TextBlock x:Name="tbOtherField" Text="{Binding otherField, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As you have observed, at this point, the contents of the intField TextBlock will be the numeric value of the intField which isn't what you want. So we're going to use a converter to format the TextBlock contents based on the value of the intField.
First, create a new class in your project, I called it intFieldConverter which implements the IValueConverter interface:
class intFieldConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var intField = (int)value;
switch (intField)
{
case 0:
return "Foo";
case 1:
return "Bar";
default:
return default(string);
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
All this does is takes the input int value and returns the string you want to show based on value. I didn't implement the method to convert it back but that's just the same but backwards; you can do that if you need two way binding.
We now need to tell your XAML about this converter. Add your converter namespace to your XAML page if you need it:
<Page
....
xmlns:converters="using:BindingTest.UI.Converter"
....>
We then need to specify the Converter resource in that converters namespace inside the Page tag:
<Page.Resources>
<converters:intFieldConverter x:Key="customIntToStringConverter"/>
</Page.Resources>
We can now use it in our TextBlock binding to convert the values and show the right string in the view:
<TextBox x:Name="tbIntField" Text="{Binding intField, Mode=OneWay, Converter={StaticResource customIntToStringConverter}}"/>
If your codebehind has the observable collection populated with some data, you should now see each row in the listview will now show either "Foo" or "Bar" based upon the value of the intField in the data.
Hope this Helps
Note:
I haven't run any of this code to test it as I don't have a machine to test it on with me at the moment. I can check when I get home to double check if you haven't been able to get it working before.

WPF: MVVM Create custom dependency property for devExpress Controls

I know you can create custom controls and dependency property for wpf controls like expained here http://msdn.microsoft.com/en-us/library/ms753358.aspx, I want to know if you can create custom dependency property in the same way for devExpress Controls ? and how ?
There is no way to bind multiple items in comboxBoxEdit control. I want to create a dependency property called SelectedItems on ComboBoxEdit.
I already created a custom property on normal ComboBox called SelectedEnumeration which binds directy to the enums and gets the value. No need to use ObjectDataProvider.
There is no way to bind multiple items in comboxBoxEdit control.
Wrong. Check DevExpress.Xpf.Editors.CheckedComboBoxStyleSettings
Basically, you can bind ComboBoxEdit.EditValue to a collection, which gets populated with the selected items.
<dxe:ComboBoxEdit ItemsSource="{Binding MyItems}"
EditValue="{Binding SelectedItems}">
<dxe:ComboBoxEdit.StyleSettings>
<dxe:CheckedComboBoxStyleSettings />
</dxe:ComboBoxEdit.StyleSettings>
</dxe:ComboBoxEdit>
ViewModel:
public class SomeViewModel
{
public ObservableCollection<MyClass> MyItems {get;set;}
public ObservableCollection<MyClass> SelectedItems {get;set;}
}
I already created a custom property on normal ComboBox called
SelectedEnumeration which binds directy to the enums and gets the
value. No need to use ObjectDataProvider.
You're putting too much responsibility on the UI, where it does not belong. Create a proper ViewModel and have your data processed by the ViewModel in such a way that it facilitates regular DataBinding to the UI. Don't resort to reflection and other types of uneeded hacks in order to put logic in the wrong layer.

What happens internally when you bind to ItemSource?

I'm curious how this works, because I have a MainViewModel, which has Property say called SubViewModel which has a Property of ObservableCollection (we'll call it Property1.)
I've implemented INotifyChangedProperty on everything.
My Main Window
<Window ..
DataContext="{Binding MainViewModel}" />
...
<StackPanel DataContext="{Binding SubViewModel}">
<local:SomeControl DataContext="{Binding}" />
</StackPanel>
</Window>
And my UserControl
<UserControl Name="SomeControl">
<DataGrid Name="MyDataGrid" ItemSource="{Binding Property1, Mode=TwoWay}" CurrentCellChanged="TestMethod" />
...
</UserControl>
In my test method, just as a test to figure out why the changes are not propegating up to the main view model I do something like this
private void TestMethod()
{
var vm = this.DataContext as SubViewModel;
var itemSourceObservableCollection = MyDataGrid.ItemsSource as ObservableCollection<MyType>;
//I thought vm.Property1 would be equal to itemSourceObservableCollection
//but they are not, itemSourceObservableCollection shows the changes I've made
//vm.Property1 has not reflected any changes made, even though I though they were the same item
}
So I figured out that ItemSource must create a copy of the item you bind it to? I'm stuck here, how do manually notify the viewModel that this property has changed and it needs to update? I thought that was INotifyPropertyChanged's job?
I think part of my problem is I lack the understanding of how this kinda works internally. If anyone can point to a good blog post, or documentation to help me understand why my code isn't working the way I expected, that would be great.
1) No copy is made.
2) ObservableCollection will propogate changes made to the collection, not the items within the collection. So you'll see additions, deletions etc. but NOT property changes to items within the collection.
3) If you want to see changes made to individual items in the ObservableCollection, you need to implement INotifyPropertyChanged on those items.
There's actually TWO different issues here. What happens internally when you bind to a collection? AND why changes on the user surface are not propagated back to your View Model. Based upon what you wrote, the two issues are not connected, but let's take them one at a time...
For the first issue... When you bind a collection, the WPF binding engine creates a "CollectionView" class that mediates between your object store and the logical tree. You can, if needed, get a copy of the the "CollectionView" using a static method on CollectionViewSource...
var cvs = CollectionViewSource.GetDefaultView(MyCollectionOfThings);
There are several interesting properties in the result, and some of them contain write accessors which allow you to directory modify the CollectionView.
For the second issue... The business classes in your SubViewModel need to inherit from INotifyPropertyChanged such that changes are 'announced' via the WPF binding engine. Your VM should be a publisher, but can also be a subscriber. A property that participates in the INotifyPropertyChanged plumbing gets declared like this...
private string _name;
[Description("Name of the driver")]
public string Name
{
[DebuggerStepThrough]
get { return _name; }
[DebuggerStepThrough]
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
This code publishes changes, but can also subscribe to changes made on the user surface by setting the appropriate attributes in your Xaml.
Background reading: What is a CollectionView?
Also, Similar question

Categories

Resources