I have this problem in my mvvm wpf application where I want to create proxied/chained databindings.
My ModelViewController looks like this:
public class ListViewModel
{
public ObservableCollection<Contact> GridData { get; private set; }
public ObservableCollection<Contact> SelectedEntities { get; private set; }
public bool IsSingeselect { get { return SelectedEntities.Count == 1; } }
public bool IsMultiSelect { get { return SelectedEntities.Count > 0; } }
public ObservableCollection<MenuComandModel> ContextMenuItems { get; private set; }
}
GridData and SelectedEntities is bound to a datagrid and works like a charm. I'm using the ContextMenuItems collection to create BarButtonItems for the datagrids contextmenu, this works very good. The MenuComandModel class has a Enabled attribute and I want to bind this on the IsSingeselect or IsMultiSelect attribute to the BarButtonItems member IsEnabled . How would I archive this?
Since you are using DevExpress you can use all the benefits of DevExpress MVVM Framework and their POCO-ViewModels:
using DevExpress.Mvvm.POCO;
//...
public class ListViewModel {
public ObservableCollection<Contact> GridData { get; private set; }
// mark the SelectedEntities property as virtual to be notified on initializing/replacing
public virtual ObservableCollection<Contact> SelectedEntities { get; private set; }
// unsubscribe the CollectionChanged event in changing-callback
protected void OnSelectedEntitiesChanging() {
if(SelectedEntities != null)
SelectedEntities.CollectionChanged -= SelectedEntities_CollectionChanged;
}
// subscribe the CollectionChanged event in changed-callback
protected void OnSelectedEntitiesChanged() {
if(SelectedEntities != null)
SelectedEntities.CollectionChanged += SelectedEntities_CollectionChanged;
UpdateSelectedEntitiesDependencies();
}
void UpdateSelectedEntitiesDependencies() {
// Raise INPC for dependent properties
this.RaisePropertyChanged(x => x.IsSingeselect);
this.RaisePropertyChanged(x => x.IsMultiSelect);
// Raise INPC for dependent properties of child ViewModels
foreach(var item in ContextMenuItems)
item.RaisePropertyChanged(x => x.Enabled);
}
void SelectedEntities_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
if(e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Reset)
UpdateSelectedEntitiesDependencies();
}
public bool IsSingeselect { get { return SelectedEntities.Count == 1; } }
public bool IsMultiSelect { get { return SelectedEntities.Count > 0; } }
public ObservableCollection<MenuComandViewModel> ContextMenuItems { get; private set; }
}
public class MenuComandViewModel {
public bool Enabled {
get {
var parentViewModel = this.GetParentViewModel<ListViewModel>();
return parentViewModel.IsMultiSelect; // Some implementation
}
}
}
Then you can bind you bar items to the ContextMenuItems collection using the approach described in MVVM Support in DXBars, DXRibbon and GalleryControl help-article.
Related
I'm writing a wpf application, where I have music albums and corresponding songs. I can add albums and corresponding songs. But now I want to to refresh the view when a change to the database is made. I found many possible solutions, but as I'm new to wpf and c# I don't know which one would suite my code.
In my MainView have an album list and a add button which opens another window where I can add data with a textbox.
AlbumListViewModel
#region Constants
IWindowManager addAlbum = new WindowManager();
IWindowManager addSong = new WindowManager();
private AlbumViewModel _selectedAlbum;
private SongViewModel _selectedSong;
#endregion
#region Constructor
public AlbumListViewModel()
{
Albums = new ObservableCollection<AlbumViewModel>(GetAlbumList());
AddAlbumCommand = new RelayCommand(x => AddAlbum());
AddSongCommand = new RelayCommand(x => AddSong());
}
#endregion
#region Properties
public ICommand AddAlbumCommand { get; private set; }
public ICommand AddSongCommand { get; private set; }
public ObservableCollection<AlbumViewModel> Albums { get; set; }
public AlbumViewModel SelectedAlbum
{
get
{
return _selectedAlbum;
}
set
{
if (_selectedAlbum != value)
{
_selectedAlbum = value;
}
NotifyPropertyChanged("SelectedAlbum");
}
}
public SongViewModel SelectedSong
{
get
{
return _selectedSong;
}
set
{
if (_selectedSong != value)
{
_selectedSong = value;
}
NotifyPropertyChanged("SelectedSong");
}
}
#endregion
#region Methods
public List<AlbumViewModel> GetAlbumList()
{
var controller = new BandManagerController();
return controller.GetAlbumList()
.Select(a => new AlbumViewModel(a))
.ToList();
}
private void AddAlbum()
{
addAlbum.ShowDialog(new AlbumViewModel(new AlbumData()));
}
private void AddSong()
{
addSong.ShowDialog(new SongViewModel(new SongData { AlbumID = SelectedAlbum.AlbumID }));
}
It opens the AlbumView where I add albums to the database.
public class AlbumViewModel : Screen
{
#region Constants
private AlbumData _data;
#endregion
#region Constructor
public AlbumViewModel(AlbumData data)
{
_data = data;
SongListVM = new SongListViewModel(data.AlbumID);
SaveAlbumToDatabase = new RelayCommand(x => AlbumToDatabase(data));
}
#endregion
#region Properties
public SongListViewModel SongListVM { get; set; }
public ICommand SaveAlbumToDatabase { get; private set; }
public string AlbumName
{
get
{
return _data.AlbumName;
}
set
{
if (_data.AlbumName != value)
{
_data.AlbumName = value;
NotifyOfPropertyChange("AlbumName");
}
}
}
public int AlbumID
{
get
{
return _data.AlbumID;
}
set
{
if (_data.AlbumID != value)
{
_data.AlbumID = value;
NotifyOfPropertyChange("AlbumID");
}
}
}
public string AlbumYear
{
get
{
return _data.AlbumYear;
}
set
{
if (_data.AlbumYear != value)
{
_data.AlbumYear = value;
NotifyOfPropertyChange("AlbumYear");
}
}
}
#endregion
#region Methods
public AlbumData AddAlbumEntry(AlbumData albumData)
{
var controller = new BandManagerController();
return controller.AddAlbumEntry(albumData);
}
public void ExecuteCancelCommand()
{
(GetView() as Window).Close();
}
public void AlbumToDatabase(AlbumData data)
{
AddAlbumEntry(data);
ExecuteCancelCommand();
}
#endregion
}
The AddAlbumEntry Method in the ALbumView is in a different class which is the connections to my database. I already use an ObservableCollection but don't know how to tell it the Database was updated.
Thanks in advance!
Just want to answer my question. I just changed my AddAlbum method to use a Deactivated event, to reload the Collection after the Dialog closes like:
private void AddAlbum()
{
var vm = new AlbumViewModel(new AlbumData());
vm.Deactivated += (s, e) => GetAlbumList();
addAlbum.ShowDialog(vm);
}
Earlier I used ListView and there was lots of events in this class, but now I started to use RecyclerView and here I can't find any event.
for example:
RecyclerView mView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
mView.ItemClick += delegate {
// action
};
is not working with this class - RecyclerView.
Actually these events whitch I want exist in RecyclerView.Adapter so I tried this:
public class ListItemArgs : EventArgs
{
public int itemPosition;
public ListItemArgs(int pos)
{
itemPosition = pos;
}
}
public class MyAdapter : RecyclerView.Adapter
{
public event EventHandler<ListItemArgs> OnItemLongClick;
// some overrides skipped (not needed anyway)
public class MyView : RecyclerView.ViewHolder
{
public View mMainView { get; set; }
public TextView mName { get; set; }
public TextView mEventCount { get; set; }
public CheckBox mFavorite { get; set; }
public MyView(View view) : base(view)
{
mMainView = view;
}
}
public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
MyView mHolder = holder as MyView;
mHolder.mMainView.LongClick += delegate
{
Snackbar.Make(mRecyclerView, "Ar tikrai norite trinti pasirinkimą?", Snackbar.LengthLong).Show();
OnItemLongClick.Invoke(mHolder.mMainView, new ListItemArgs(position));
};
}
}
and declarations in other class:
RecyclerView mView = FindViewById<RecyclerView>(Resource.Id.recyclerView);
RecyclerView.Adapter mAdapter new AddNewDayAdapter(mView);
And now I should be able to launch this event from mAdapter object, which leter I use to SetAdapter for my RecyclerView. (code below)
mAdapter.OnItemLongClick += (object sender, ListItemArgs e) =>
{
// action
}
mView.SetAdapter(mAdapter);
but mAdapter.OnItemLongClick is not working.
Main question: how can I get an event in which args would be view of row and selected item position ?
It's a closed network and I don't have a VS on the internet computer, so sorry in advance for how the code looks.
Here is part of my view model:
public class Elements : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
private int _CardNumber;
private int _CardCode;
private Card _CurrentlySelectedRow;
public int CardNumber
{
get { return _CardNumber; }
set
{
if (value != _CardNumber)
{
_CardNumber = value;
OnPropertyChanged("CardNumber");
}
}
}
public int CardCode
{
get { return _CardCode; }
set
{
if (value != _CardCode)
{
_CardCode = value;
OnPropertyChanged("CardCode");
}
}
}
public Card CurrentlySelectedRow
{
get { return _CurrentlySelectedRow; }
set
{
if (value != _CurrentlySelectedRow)
{
_CurrentlySelectedRow= value;
OnPropertyChanged("CurrentlySelectedRow");
}
}
}
public class Card
{
public int CardNumber { get; set; }
public int CardCode { get; set; }
public Card() {}
public Card(int CardNumber_, int CardCode_)
{
CardNumber = CardNumber_;
CardCode = CardCode_;
}
}
private ObservableCollection<Card> _Cards { get; set; }
public ObservableCollection<Card> Cards
{
get
{
if (_Cards == null)
_Cards = new ObservableCollection<Card>();
return _Cards;
}
set
{
_Cards = value;
OnPropertyChanged("Cards");
}
}
public bool UpdateCard()
{
//selected row in gridcontrol is binded to CurrentlySelectedRow
CurrentlySelectedRow.CardNumber = CardNumber;
CurrentlySelectedRow.CardCode = CardCode ;
}
public bool AddCard()
{
Cards.Add(new Card(CardNumber, CardCode );
}
}
Since the grid is not editable, the row is updated in external form, in which controls are binded to cardNumber and cardCode, and when pressing OK - the UpdateCard() is called (if we in update), and the AddCard called if we in add.
The AddCard works - the grid updates.
The UpdateCard - updates the list, but the grid isn't updated...
Maybe you can see were is the problem?...
My intial thought on this is that the update to the CurrentlySelectedRow.CardNumber and CurrentlySelectedRow.CardCode wont call OnPropertyChanged().
With ObservableCollection the UI will be updated if any items are added or removed but will not if any changes are made to the properties in an item.
Because no call is made, the UI doesn't know that these properties have changed.
You would have to make your Card class implement INotifyPropertyChanged similarly to Elements.
What I would normally do is create a BaseObject class that simply implements INotifyPropertyChanged. This class is then inherited by all of my other classes:
public abstract BaseObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
Then just change the properties in your Card class to call OnPropertyChanged.
Edit following on from comments:
Now that you've got INotifyPropertyChanged implemented you could try rebinding whatever is bound to Elements.CardNumber and Elements.CardCode to CurrentlySelectedCard.CardNumber and CurrentlySelectedCard.CardCode respectively. This would potentially allow you to delete the properties from Elements.
This will, however depend on the rest of your code.
I have a model collection that is referenced from a most parts of my code.
public class TraceEntryQueue
{
private readonly Queue<TraceEntry> _logEntries;
public TraceEntryQueue()
{
_logEntries = new Queue<TraceEntry>();
}
public void AddEntry(TraceEntry newEntry)
{
_logEntries.Enqueue(newEntry);
}
public List<TraceEntry> GetAsList()
{
return _logEntries.ToList();
}
}
This collection is presented in one of my views.
public class LoggingViewModel : ViewModelBase
{
private ICollectionView _customers;
public ICollectionView Customers
{
get
{
return _customers;
}
private set
{
_customers = value;
}
}
public LoggingViewModel(TraceEntryQueue traceQueue)
{
Customers = CollectionViewSource.GetDefaultView(traceQueue.GetAsList());
Customers.SortDescriptions.Add(new SortDescription("Index", ListSortDirection.Descending));
Customers.Refresh();
}
}
The question is how I can notify my view to reload the collection when I add new entries via
public void AddEntry(TraceEntry newEntry)
{
_logEntries.Enqueue(newEntry);
}
Use an observable collection instead of a queue. This will automatically notify your view when the collection is updated (add/remove etc.)
public class TraceEntryQueue
{
private readonly ObservableCollection<TraceEntry> _logEntries;
public TraceEntryQueue()
{
_logEntries = new ObservableCollection<TraceEntry>();
}
public void AddEntry(TraceEntry newEntry)
{
_logEntries.Add(newEntry);
}
public ObservableCollection<TraceEntry> GetLogEntries()
{
return _logEntries;
}
}
public class TraceEntryQueue
{
private readonly Queue<TraceEntry> _logEntries;
private readonly ICollectionView _logEntriesView;
public TraceEntryQueue()
{
_logEntries = new Queue<TraceEntry>();
_logEntriesView = CollectionViewSource.GetDefaultView(logEntries.ToList());
}
//....
public void AddEntry(TraceEntry newEntry)
{
_logEntries.Enqueue(newEntry);
_logEntriesView.SourceCollection = logEntries.ToList();
_logEntriesView.Refresh();
}
public ICollectionView GetAsView()
{
return _logEntriesView;
}
}
Use GetAsView and bind directly to your datagrid / listbox / listview.
As #RobJohnson mentionned, you can use an ObservableCollection. However, if you need to create your own collection class, you should implement the INotifyCollectionChanged interface:
public class TraceEntryQueue : INotifyCollectionChanged
{
private readonly Queue<TraceEntry> _logEntries;
public TraceEntryQueue()
{
_logEntries = new Queue<TraceEntry>();
}
public void AddEntry(TraceEntry newEntry)
{
_logEntries.Enqueue(newEntry);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newEntry));
}
public List<TraceEntry> GetAsList()
{
return _logEntries.ToList();
}
public event NotifyCollectionChangedEventHandler CollectionChanged;
protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
{
CollectionChanged(this, e);
}
}
}
How do I change the value of TotalPublicationsRead, when the Read property of a Publication has changed?
public class Report
{
public ObservableCollection<Publication> Publications { get; set; }
public int TotalPublicationsRead { get; set; }
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set
{
if (this.read!= value)
{
this.publications = value;
OnPropertyChanged("Read");
}
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
private void OnPropertyChanged(string property)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Thanks in advance.
If you're trying to do what I think you are, then I'd change the TotalPublicationsRead property and forget about the events. In the code below I just count the items in the list where the Publication has been Read.
The way you were trying to do it you would have to have an event handler for when the ObserableCollection changed. Then you would have to attach an event handler to the PropertyChanged event which would increment or decrement the TotalPublicationsRead property. I'm sure it would work, but it would be a lot more complicated.
public class Report
{
public List<Publication> Publications { get; set; }
public int TotalPublicationsRead
{
get
{
return this.Publications.Count(p => p.Read);
}
}
}
public class Publication : INotifyPropertyChanged
{
private bool read;
public bool Read
{
get { return this.read; }
set { this.read = value; }
}
}
You can use Dependency Property.
Please check details at:
http://www.wpftutorial.net/dependencyproperties.html
http://msdn.microsoft.com/en-us/library/ms745795(v=vs.110).aspx