ListView with custom item layout in C# - c#

I have a class called EventBox that extends TableLayoutPanel. It's a table with one single row and dynamically adjusting number of columns.
During its lifecycle, this EventBox adds/removes items from itself (buttons, combo boxes etc).
What I want is to create a ListView (or something similar) that would contain multiple EventBox objects and visually display them in a list.
I've created a class called TestEventList, but I do not know what to extend!
I've tried TableLayoutPanel (I believe it's overkill), ListBox (wrong!) and now ListView.
However, ListView's Items property has a method Add which only accepts ListViewItem objects as parameters.
How can I describe my EventBox as a ListViewItem?
Or better yet, what other choices do I have?
EDIT: I obviously want the list to be able to keep track of its items: add, remove at index etc.

Firstly, ListView will not do anything on its own. You need to set ListView.View to an instance of GridView.
I recently had to solve the dynamic column problem. The solution I chose is bindable and MVVM compatible, just in case you want to use that pattern (i was). I created a behavior (to avoid extending GridView) that will dynamically inject and remove columns as a source structure updates. This behavior needs dependency property that you bind to a instance of a class that defines the columns. The column class should allow you to define columns where a column is the property you are binding to on the source data, and a key (to represent the cell type).
public class ColumnDefinition
{
public string Key{ get; set}
public string ContentBindingPath { get; set;}
}
When the columns structure changes, the behavior builds and injects (or removes) columns into the attached GridView. The behavior builds each column based upon a series of key/value pairs defined on the behavior. This is to allow the XAML to specify the cell template to apply to the new columns, enforcing seperation of concerns.
public class CellTemplateDefinition
{
public string Key { get; set; }
public DataTemplate ColumnTemplate { get; set;}
}
public class DynamicColumnBehavior: Behavior<GridView>
{
public IEnumerable<ColumnDefinition> Columns
{
get { return (IEnumerable<ColumnDefinition>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
// Using a DependencyProperty as the backing store for Columns. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(IEnumerable<ColumnDefinition>), typeof(DynamicColumnBehavior), new UIPropertyMetadata(null));
public static void OnColumnsChanged(DependencyObject sender, DependencyPropertyChangedEventArgsargs)
{
DynamicColumnBehavior behavior = sender as DynamicColumnBehavior;
if(behavior != null) behavior.UpdateColumns();
}
public IEnumerable<CellTemplateDefinition> Cells { get; set; }
private void UpdateColumns(){ throw new NotImplementedException("I left this bit for you to do ;)");}
}

Related

WPF Bind once and never update?

I have a ListView and a GridView that lists users in an application by names. Whenever the user selects an user to edit, I add a new tab to a TabControl, and bind all editable properties to the WPF controls.
However, when the user is editing in the Edit Tab, the information in the List (specifically, the name field) is also being updated.
Currently I'm making a copy of the object to be edited and leaving the original so it doesn't update the ListView, but isn't there a better/easier way to do this?
I've tried setting the Binding Mode=OneWay, didn't work, and also UpdateSourceTrigger=Explicit in the GridView but also didn't work.
Is there any easier way to do this?
Edit: The way I implemented my INotifyPropertyChanged class is part of the issue, since I have this:
public partial class MyTabControl : UserControl
{
public MyTabControl()
{
InitializeComponent();
//Here, DataContext is a List<Users>
//Users being my Model from the Database
//Some of it's properties are bound to a GridView
//User doesn't implement INPC
}
public void OpenTab(object sender, RoutedEventArgs e)
{
User original = (sender as Button).DataContext as User;
// - This will create a new ViewModel below with the User I'm sending
MyTabControl.AddTab(original);
}
}
And my ViewModel of Users is:
public class UserViewModel : INotifyPropertyChanged
{
public User Original { get; private set; }
public string Name { get { return Original.Name; } set { Original.Name = value; OnPropertyChanged("Name"); } }
public UserViewModel(User original)
{
Original = original ?? new User();
}
// - INPC implementation
}
Since my ViewModel is the one reporting the property changes, I didn't expect my original User to report it as well to the GridView.
The Mode=OneWay causes the information flow to go from the bound data entity to the target UI property only, any change to the UI property will not be bound back.
The reason why the UI content is changing is because the underlying property is read/write (i.e. has a getter and a setter) and is notifying any value change (due to the implementation of the INPC interface).
Presuming that it is a list of User objects you've bound to the GridView, you have two simple options to fix this. Which one is best depends on how much scope for change you have:
change the current Name property on the User object, remove the setter for it. Replace the setter with a method to set the property (i.e. SetUserName(string name)) which then sets the private member variable. Or pass the name as an argument to the constructor of the User entity.
create a new property with only a getter which returns the name and set your binding to that; i.e. public string UserName { get { return Name; }}. As there is only a getter there will be no notification of this property, so if the name does change it won't be propagated via this new property.

C# WPF MVVM ComboBox Binding

First of all, what I'm trying to do is a "simple" binding of a ComboBox to my source.
The structure is something like:
public class Data
{
public ObservableList<string> List {get;set;}
public string Selected {get;set;}
}
Also, it implements INotifyPropertyChanged interface.
My problem is, i found several solutions to do this via XAML, unfortunately i can't do it with XAML since my ComboBoxes have to be generated during runtime.
So my question is, how i can bind my ComboBox to Data.List, and also the selected item (value?) to Data.Selected, and this one should be TwoWay so my Data class knows that something was selected. Keep in mind this has to be through c# code (XAML is no option unfortunately).
Thanks in advance. :)
It's pretty easy. Assuming, that Data has properties instead of fields:
public class Data
{
public Data()
{
List = new ObservableCollection<string>
{
"Apple", "Orange", "Lime"
};
}
public ObservableCollection<string> List { get; private set; }
public string Selected { get; set; }
}
you can write this:
var comboBox = new ComboBox
{
DataContext = new Data()
};
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding("List"));
comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding("Selected")
{
Mode = BindingMode.TwoWay
});
To add ComboBox into visual tree, just call proper method for the container. E.g., this will work with any ContentControl (like Window):
AddChild(comboBox);
how i can bind my combobox to Data.List, and also the selected item (value?)
Create a custom composite user control which contains the combobox. Map the combobox's properties to two dependencies properties created on the custom control, one to load the data and the other to provide an on demand selected item's data. Any plumbing needs are done inside the codebehind which ultimately provides all the magic.
Then you can create/bind this control dynamically in codebehind as needed in the other page you are working on.
Sounds like a sort of "recursive binding". If your combos are in a container control, what you need is bound the container to a collection of your single combo model, so each view in the ItemsControl will be bound to a single combo model.

WPF nested ListBox control does not update the list it is bound to

I have the following two classes (with altered names), that will be used to populate a ListView with a nested Listbox
private class ObjectName //For the Listview
{
public int ID { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
public List<BabyObject> Field3 { get; set; }
}
private class BabyObject //For the nested Listbox
{
public string Field1 { get; set; }
public bool Field2 { get; set; }
}
I populate an Observable Collection with some objects and set it as the ItemsSource of my ListView.
My ListView correctly displays the Objects in each ListViewItem, and the nested listbox in each item correctly displays each BabyObject.
The Problem:
If I edit the ID, Field1 or Field2 values using the UI, the Observable Collection in memory also updates - of course it does, because of the binding. However if I edit any of the BabyObject's values, the List in memory doesn't change.
Therefore, when I change the Controltemplate of the ListView (so that values are displayed in labels instead of textboxes), the BabyObject values change back to their original.
I know people often have trouble updating the interface when Lists are updated in code, but not this way round?
I'll be happy to answer questions and include more code but the XAML is rather large. In the meantime I am going to make a separate example listview to see if I can test this on a smaller control.
Thanks
You need to raise a property change notification when the properties are set - this tells the bindings to update.
MSDN docs here
I have solved the problem, however I have no idea what was causing it.
I tried setting the Binding of the Baby Objects to a variety of Modes, and changed the List to an Observable Collection.
This did not work, so I manually undone the changes (Not setting the mode and using a List) - and now it works fine.
If I edit the BabyObject values using the interface, the changes are also made to the List.
I am baffled too - sorry for wasting your time!

Avoid data duplication in child class mvvm

I'm working on an windows phone app where I'm using MVVM pattern with it. In short, I've got a list which is binded to class. This class contains an observable collection which store information for each individual rows.
Is there a way to avoid duplicating data in a child class when only one instance of the specific data is required to be set in a parent class. Think of the following scenario:
Grid
- TableName
- Rows
As mentioned, the rows property is an Observable Collection i.e. ObservableCollection where the row is a class made up of numerous properties such as Id, Code, Name, etc...
This observable collection is binded to a list and each row's viewmodel has a action binded to an ICommand to it so that when it is tapped, it will trigger an event and I will be able to redirect to another page but I'll be able to use the row's information i.e. Id, Code, etc...
The problem is that I need access to the TableName and while I could include it as one of the row's class property, it seems pointless as this will be the same value over and over again.
What is the proper way to design this? Is there a specific pattern that applies to this scenario or is just a case of duplicating the data in each of the rows... It just doesn't feel right!
Thanks.
T.
Why don't you create the ICommand in the TableNameVM and it gets passed to each child RowVM to expose as a simple property. If the implementation of the command inside TableNameVM needs the Row, then just pass it as the command parameter.
You could even forego adding it the RowVM, and just use RelativeSource on the row binding to bind to the TableName instead. However, I must admit I dislike this kind of short cutting in the view.
Something like this:
public class TableNameVM : ViewModelBase
{
private readonly ICommand myCommand;
public ObservableCollection<RowVM> Rows { get; set; }
public TableNameVM()
{
this.myCommand = new DelegateCommand<RowVM>(ExecuteMyCommand);
//create 10 rows
for(int n = 1; n < 10; n++)
this.Rows.Add(new RowVM(this.myCommand));
}
private void ExecuteMyCommand(RowVM row)
{
//do whatever
}
}
public class RowVM : ViewModelBase
{
public ICommand MyCommand { get; private set; }
public RowVM(ICommand myCommand)
{
this.MyCommand = myCommand;
}
}
Your Row button to fire the command would look something like:
<Button Command="{Binding MyCommand}" CommandParam="{Binding}" />

WPF: Best way to create bindings to unknown types in MVVM

I am looking for a way to display data in a DataGrid from types that are unknown at compile-time.
I have the following base class
public abstract class Entity
{
// Some implementation of methods ...
}
In run-time, I load a plug-in DLL and use reflection to get a list of all the types derived from Entity. For example:
public class A : Entity
{
public LocalAddress Address{ get; set; }
}
public class B : Entity
{
public Vendor Vendor { get; set; }
public string Name { get; set; }
}
Then I retreive a list of their instances from DB
public IEnumerable<Entity> Entities { get; set; } // A list of instances of type A for example
Entities is the DataGrid's ItemsSource, But what's the best way I can bind the properties to the DataGrid?
Since the properties can be complex, I also need to be able to bind to a specific path, for example Address.HomeNum ...
Clarifications
I only need to show a one grid of a type's instances at a time. The complete scenario is this:
I get a list of types that derive from Entity from the plug-in DLL through reflection
I show their names in a List. (in this example that list will contain A and B
When the user clicks on a specific item, let's say A, I get a list of A instances from DB - so far so good.
I want to display that list of A's instances in a DataGrid.
When the user selects another item from the list (meaning another type, lets say B), I get a list of B's instances from DB and need to display those in the grid and so on ...
The plug-in DLL is a class library with no xamls (also my users are the ones making this plug-ins and I don't want them to have to write DataTemplates for their entities.
I also can't make predifned DataTemplates as I don't know the types I'll need to display until run-time. Each type can have different types and amount of properties. All I know in complie-time is that they all derived from Entity.
The grid should also be editable.
A DataGrid seems inappropriate in this case. If your list was bound to two separate entities, it would break badly.
A better option would potentially be to use some other ItemsControl and set up a DataTemplate for each type of Entity. This would allow you to build custom editors per entity, and have a "list" of them to edit.
If you know the entities will always be of a single type, I'd instead build the collection of that specific type, and bind to it.
Since you don't know the property names of the Entities beforehand, I think your best option is to keep your DataGrid in Xaml but move the defintion and the Bindings of its DataGridColumns to the code behind.
AddColumnsForProperty(PropertyInfo property, string parentPath = "")
{
var title = property.Name;
var path = parentPath + (parentPath=="" ? "" : ".") + property.Name;
if(property.PropertyType == typeof(string))
{
var column = new DataGridTextColumn();
column.Header = title;
column.Binding = new Binding(path);
dataGrid.Columns.Add(column);
}
else if(property.PropertyType == typeof(bool))
{
//use DataGridCheckBoxColumn and so on
}
else
{
//...
}
var properties = property.GetProperties();
foreach(var item in properties)
{
AddColumnsForProperty(item, path);
}
}
Now if you execute these you'll have your dataGrid columns filled. and by adding all instances of the desired type in an observable collection and bind it to ItemsSource of the DataGrid it should work. selectedItem should be an instance of one the classes derived from Entity. The listbox contains new A() and new B() (or any existing instances of A and B) so selectedItem can be used in the following statement.
var propertyList = selectedItem.GetType().GetProperties();
foreach (var property in propertyList)
AddColumnsForProperty(PropertyInfo property);
how to write DataGridColumnTemplate in code
Edit:
Member can't be used in this scenario because INotifyPropertyChanged should get involved, so I replaced members with properties.
I would use attributes to specify what exactly is bindable (including composite object):
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public abstract class EntityAttribute : Attribute
{
internal abstract IEnumerable<EntityColumn> GetColumns(object instance, PropertyInfo property);
}
This attribute supports plain properties as well as composite structures. You should simply inherit and implement the method.
EntityColumn represents single value. Simplified version can be implemented like this:
public class EntityColumn
{
private readonly Action<object> _setMethod;
private readonly Func<object> _getMethod;
public string Caption { get; private set; }
public object Value
{
get { return _getMethod(); }
set { _setMethod(value);}
}
internal EntityColumn(string caption, Action<object> setMethod, Func<object> getMethod)
{
_getMethod = getMethod;
_setMethod = setMethod;
Caption = caption;
}
}
Later you can create single DataTemplate for EntityColumn and use it for all properties for all possible entities. Entity Object will contain additional method to return all EntityColumn relevant to it:
public IList<EntityColumn> GetColumns()
{
var objectType = GetType();
var properties = objectType.GetProperties();
return properties.SelectMany(
p => p.GetCustomAttributes<EntityAttribute>().SelectMany(a => a.GetColumns(this, p))).ToList();
}
For collection of Entities you can introduce EntityCollection which will absorb column information and provide structure similar to DataSet.
This implementation gives you flexibility of dynamic structure and keeps almost everything strongly typed. You can even extend attributes and EntityColumn to support validation.
As of displaying object, you'd rather use ItemsControl or even self written control inherited from ItemsControl to take advantage of knowing about Entity and EntityCollection classes.

Categories