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}" />
Related
I am still baffled finding out how to get a DataGridView updated automatically when changing the content of its DataSource without explicitely triggering DataGridView.Update(). It seems there is no difference at all between DataTable, List, BindingList as (direct) DataSource and as (indirect) DataSource with an additional BindingSource which uses any of the former as DataSource.
The DataGridView I am actually using for this is non-editable and just shows entries which are updated by the corresponding entity code. My last attempt was with a BindingSource that uses a BindingList and manipulating the content of the BindingSource within the code.
I have omitted some methods here, which do not play a role for the basic problem.
Form:
private void FormLog_Load(object sender, EventArgs e) {
...
dgvLog.DataSource = Log.Current.SourceEntries;
...
}
private void ClearLog() {
Log.Current.RemoveAll();
}
public void UpdateDataSource() {
dgvLog.Update();
}
Entity (singleton class):
public class LogEntry {
public int ID { get; set; }
public string DateTime { get; set; }
public string Type { get; set; }
public string Event { get; set; }
public string Details { get; set; }
}
public class Log {
public BindingList<LogEntry> Entries { get; set; }
public BindingSource SourceEntries { get; set; }
public Log() {
Entries = new BindingList<LogEntry>();
SourceEntries = new BindingSource() { DataSource = Entries };
ReadAll();
}
public void Add(string type, string logEvent, string details = "") {
LogEntry entry = MapToDB(new LogEntry() {
Type = type,
Event = logEvent,
Details = details
});
DB.Write(QueryAdd(entry));
SourceEntries.Add(entry);
if (Config.Current.GetForm("Log") != null)
((FormLog)Config.Current.GetForm("Log")).UpdateDataSource();
}
public void ReadAll() {
for (int i = SourceEntries.Count - 1; i >= 0; i--) {
SourceEntries.RemoveAt(i);
}
DataTable dt = DB.Read(QueryReadAll());
if (dt != null) {
foreach (DataRow row in dt.Rows) {
SourceEntries.Add(MapToList(row));
}
}
if (Config.Current.GetForm("Log") != null)
((FormLog)Config.Current.GetForm("Log")).UpdateDataSource();
}
public void RemoveAll() {
DB.Write(QueryRemoveAll());
for (int i = SourceEntries.Count - 1; i >= 0; i--) {
SourceEntries.RemoveAt(i);
}
Add("I", "Log cleared");
}
This works but only when I call UpdateSource() which calls dgvLog.Update() by using a selfwritten FormStack in another singleton class which I would like to avoid. Of course, one could simply call dgvLog.Update() within the form itself but, esp. with this log example, it is obvious that this does not help when updating data from/within another form while the form that displays the DataGridView is still opened in the background.
Also, as there is no difference (between using DataTable or List, etc. and BindingSource or not) I wonder what the benefit/purpose of BindingList and BindingSource are:
Is this the correct approach or am I missing something!?
By the way, I am using .NET v4.5.2.
It seems there is no difference at all between DataTable, List, BindingList as (direct) DataSource and as (indirect) DataSource with an additional BindingSource which uses any of the former as DataSource.
A BindingSource has a few uses
maintains knowledge of position/current row and can thus achieve shared navigation (a dgv and textboxes all bound to the same BS means the dgv can navigate through records and the textboxes update because they always show "current row")
provides sorting and filtering facilities
supports complex binding scenarios where it must help filter a list down to only children of some currently selected parent in a different bindingsource
provides separation for multi different positional browsing of a common DataSource
works but only when I call UpdateSource() which calls dgvLog.Update() by using a selfwritten FormStack in another singleton class which I would like to avoid. Of course, one could simply call dgvLog.Update() within the form itself but, esp. with this log example, it is obvious that this does not help when updating data from/within another form while the form that displays the DataGridView is still opened in the background.
Datagridview.Update() is concerned with repainting areas for the control that need it; it is nothing to do with committing changes to underlying data models. Perhaps you need EndEdit which finishes editing operations on the current row and commits them to the underlying data storage. This also happens when you click a different row in a grid. Bindingsource also have an EndEdit method. Mostly you don't need to call these methods yourself
To share data between forms pass the datatable the data is stored in and bind it through a bindingsource in the second form
Also, as there is no difference (between using DataTable or List, etc. and BindingSource or not) I wonder what the benefit/purpose of BindingList and BindingSource are:
DataTable is a collection of DataRow. A DataRow is at it's heart an object array. The end
Binding list is a list of whatever you want, such as your custom class Person. Arguably more useful ultimately, but it's comparing apple's and oranges. If instead you open up the DataSet Designer then you can specify tables that have named typed column. In that regard it's not a huge amount different from writing your own classes (it writes a large amount of good code on a short time though. I use them as data models sometimes
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.
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.
my ViewModel has a IReactiveList which is bound to a grid in the View. I now want to filter the data based on user-driven events. My question is what is the best approach for this?
The only way I can see is to create a new ReactiveList instance containing just the filtered data and set it to the ViewModel IReactiveList property each time the filter event changes. I don't like this as I'd be creating new ReactiveList instance on each filter event.
Is there a better way? Obviously I could directly manipulate the View Grid filters in the VM but this would break the MVVM-ness.
Thanks a lot.
Since IReactiveList implements INotifyCollectionChanged, can't you just expose a CollectionView from your VM instead and change the filtering on that?
public class MyVM
{
private readonly IReactiveList data;
//bind grid to this
public ListCollectionView DataCollectionView { get; private set; }
public MyVM(IReactiveList data)
{
this.data = data;
this.DataCollectionView = new ListCollectionView(this.data);
this.DataCollectionView.Filter = FilterData;
}
private bool FilterData(object o)
{
//filter your data how ever you want in here.
}
}
At the point your filter changes, just call this.DataCollectionView.Refresh() and the data will be refiltered.
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 ;)");}
}