I developed an application on Windows 10 Universal App who use MVVM but I have a big problem with it.
I would add an ObservableCollection item(created on a second window) to the MVVM and then, show the new item on the ListView of MainPage but it doesn't refresh!
The 2 windows are always open
http://i.stack.imgur.com/WSo6v.jpg
The code of MVVMList.cs
public class MVVMList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<THEFile> onglets_cache = new ObservableCollection<THEFile>();
public ObservableCollection<THEFile> onglets_list
{
get
{
return onglets_cache;
}
set
{
onglets_cache = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
public MVVMList()
{
onglets_list = new ObservableCollection<THEFile>();
Fonctions fonctions = new Fonctions();
fonctions.LoadOnglets(onglets_cache);
}
}
The code of the second page(always open) - CreateFile.xaml.cs
private void create_butt_Click(object sender, RoutedEventArgs e)
{
Fonctions fonc = new Fonctions(); MVVMList main = new MVVMList();
fonc.SetupNew(main.onglets_list, "test" + ".php", "");
}
//SetupNew on Fonctions.cs
public async void SetupNew(ObservableCollection<THEFile> list, string name, string content)
{
FolderPicker folderpick = new FolderPicker();
folderpick.ViewMode = PickerViewMode.List;
folderpick.FileTypeFilter.Add(".html"); folderpick.FileTypeFilter.Add(".htm"); folderpick.FileTypeFilter.Add(".HTML");
folderpick.FileTypeFilter.Add(".php"); folderpick.FileTypeFilter.Add(".PHP");
folderpick.FileTypeFilter.Add(".css"); folderpick.FileTypeFilter.Add(".CSS");
folderpick.FileTypeFilter.Add(".js"); folderpick.FileTypeFilter.Add(".JS");
StorageFolder storage_file = await folderpick.PickSingleFolderAsync();
if (storage_file != null)
{
MainPage vm = new MainPage();
list.Add(new THEFile { NameOfFile = name, PathOfFile = storage_file.Path + "\\" + name, CodeOfFile = content, already_opened = false, line = 0 });
string path = storage_file.Path + #"\" + name;
StorageFile file_create = await storage_file.CreateFileAsync(name, CreationCollisionOption.GenerateUniqueName);
Windows.Storage.AccessCache.StorageApplicationPermissions.FutureAccessList.Add(file_create);
SaveOnglets(list);
}
}
And on the MainPage.xaml (always open)
...
<ListView x:Name="onglets" x:FieldModifier="public" ItemTemplate="{StaticResource Templa}" ItemsSource="{Binding onglets_list}" SelectionChanged="onglets_SelectionChanged" Margin="0,117,0,57" Visibility="Visible" ContainerContentChanging="onglets_ContainerContentChanging">
...
Thank you!
In your XAML, try using a Collection View Source.
Add this to the top of your xaml:
<Page.Resources>
<CollectionViewSource x:Name="MakesCollectionViewSource" IsSourceGrouped="True"/>
</Page.Resources>
Set your ListView:
ItemsSource="{Binding Source={StaticResource MakesCollectionViewSource}}"
Then in your code when you have a List of items assign it using
MakesCollectionViewSource.Source = /* Some List<GroupInfoList<object>> that is generated from onglets_list*/
I create my List like this but it may not be relevant because this is to make all of my object names alphabetical:
internal List<GroupInfoList<object>> GetGroupsByLetter()
{
var groups = new List<GroupInfoList<object>>();
var query = from item in MakeList
orderby ((Make)item).MakeName
group item by ((Make)item).MakeName[0] into g
select new { GroupName = g.Key, Items = g };
foreach (var g in query)
{
var info = new GroupInfoList<object>();
info.Key = g.GroupName;
foreach (var item in g.Items)
{
info.Add(item);
}
groups.Add(info);
}
return groups;
}
public class GroupInfoList<T> : List<object>
{
public object Key { get; set; }
public new IEnumerator<object> GetEnumerator()
{
return (System.Collections.Generic.IEnumerator<object>)base.GetEnumerator();
}
}
where MakeList is my observable collection and Make are the objects in the collection and MakeName is a string I am trying to alphabetize
And then call using
MakesCollectionViewSource.Source = GetGroupsByLetter();
If I understand your code and requirements correctly, I think part of the problem is that you "new up" your MVVMList and your MainPage everytime you click the create button.
So, without getting into suggestions about using MVVM Light and an IOC container, you could quickly accomplish what you're trying to do by making your MVVMList class a singleton and having your MainPage use it for a data context. When your other window adds to the MVVMList.onglets collection, it will be immediately reflected in your currently open MainPage. Let me know if you need some code snippets. Good luck!
[Edit below]
I had a few minutes left on lunch, so here is an over-simplified example. Again, without getting into what MVVM is and is not. Personally, I would do this differently, but that would be outside the scope of your question. Full disclosure - this is in WPF, but same logic applies, I just don't have Windows 10 on the PC that I'm using. I also simplified the collection to be of type string. This is not intended to copy/paste into your code as it will not work in your example - but should easily transfer.
MVVMList class:
public class MVVMList: INotifyPropertyChanged
{
//Singleton section
private static MVVMList instance;
private MVVMList() { }
public static MVVMList Instance
{
get
{
if (instance == null)
{
instance = new MVVMList();
}
return instance;
}
}
//end singleton section
private ObservableCollection<string> _onglets = new ObservableCollection<string>();
public ObservableCollection<string> Onglets
{
get { return _onglets; }
set
{
if (_onglets != value)
{
_onglets = value;
if (PropertyChanged != null)
PropertyChanged.Invoke(this,
new PropertyChangedEventArgs("onglets_list"));
}
}
}
//INotify implementation
public event PropertyChangedEventHandler PropertyChanged;
}
MainPage:
<ListView x:Name="onglets" x:FieldModifier="public" ItemsSource="{Binding Onglets}" />
MainPage.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = MVVMList.Instance;
Loaded += MainWindow_Loaded;
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var x = new CreateWindow();
x.Show();
}
}
CreateWindow.cs:
private void CreateButton_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty(StringTextBox.Text))
{
MVVMList.Instance.Onglets.Add(StringTextBox.Text);
}
}
Related
This my view Main:
XAML View Main :
<DataGrid AutoGenerateColumns="false"
SelectedItem="{Binding Selectedrole}"
ItemsSource="{Binding RoleList}">
<Button Content="Add Role"
Command="{Binding AddRole}"
Height="35"/>
</DataGrid>
The ViewModel Main:
public RoleManagementViewModel()
{
roleList = new ObservableCollection<UserRoleClass>(WCFclient.GetAllRoles());
_addRole = new RelayCommand<string>(AddRoleFunction);
}
private void AddRoleFunction(object obj)
{
if (!Application.Current.Windows.OfType<SelectionCompartementView>().Any())
{
AddRoleView winAddRole = new AddRoleView();
winAddRole.DataContext = new AddRoleViewModel();
winAddRole.Show();
winAddRole.Topmost = true;
winAddRole.Focus();
}
}
public ObservableCollection<UserRoleClass> RoleList
{
get { return roleList; }
set
{
roleList = value;
OnPropertyChanged("RoleList");
}
}
The view Add Role:
Xaml Add-Role:
<Button x:Name="button1"
Command="{Binding SaveRole}"
CommandParameter="{Binding ElementName=AddRole}"/>
ViewModel Add-Role:
public AddRoleViewModel()
{
_addOrUpdate = new UserRoleClass();
_addOrUpdate = new UserRoleClass();
saveRole = new RelayCommand<Window>(addFunc);
}
private void addFunc(Window window)
{
UserRoleClass newRole = new UserRoleClass()
{
name = AddOrUpdate.name,
description = AddOrUpdate.description,
};
int resultSave = WCFclient.saveRole(newRole);
if (resultSave == 0)
{
String UpdateInformation0 = "Role is saved successfully";
string sCaption = "Save Role";
MessageBoxButton btnMessageBox = MessageBoxButton.OK;
MessageBoxImage icnMessageBox = MessageBoxImage.Information;
MessageBoxResult rsltMessageBox = MessageBox.Show(
UpdateInformation0, sCaption, btnMessageBox, icnMessageBox);
}
if (window != null)
{
window.Close();
}
}
private ICommand saveRole;
public ICommand SaveRole
{
get { return saveRole; }
}
It works fine: when I add a new Role, the view of Add-Role closes and returns to the view Main, and I have a result in database... but not in the DataGrid in MainView.
How can I refresh directly?
First, why do you have two of the following line?
_addOrUpdate = new UserRoleClass();
Second, when you save your new role, it seems you're calling a WCF service that saves it to the DB.
You're using an observable collection which should update when you add to it, but I don't see your code adding the new role into the RoleList.
The fastest way to do it is with something like this.
public AddRoleViewModel(Action<UserRoleClass> onAdded = null)
{
_addOrUpdate = new UserRoleClass();
_addOrUpdate = new UserRoleClass();
_onAdded = onAdded;
saveRole = new RelayCommand<Window>(addFunc);
}
private void addFunc(Window window)
{
UserRoleClass newRole = new UserRoleClass()
{
name = AddOrUpdate.name,
description = AddOrUpdate.description,
};
int resultSave = WCFclient.saveRole(newRole);
if (resultSave == 0)
{
String UpdateInformation0 = "Role is saved successfully";
string sCaption = "Save Role";
MessageBoxButton btnMessageBox = MessageBoxButton.OK;
MessageBoxImage icnMessageBox = MessageBoxImage.Information;
MessageBoxResult rsltMessageBox = MessageBox.Show(UpdateInformation0, sCaption, btnMessageBox, icnMessageBox);
}
}
_onAdded?.Invoke(newRole);
if (window != null)
{
window.Close();
}
And when you create the ViewModel
new AddRoleViewModel(newItem=>{ RoleList.Add(newItem); });
But I can't say I like the architecture all that much. If might want to look at some sort of a messenger service
This is a common problem with binding datagrid to an observable collection. The binding updates if the collection changes not if the collection content changes. The best way to do this, after adding the new UserRole to the RoleList is to use a "deep" observable collection like this one:
public sealed class DeepObservableCollection<T>
: ObservableCollection<T> where T : INotifyPropertyChanged {
public event PropertyChangedEventHandler ItemPropertyChanged;
public DeepObservableCollection() {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
public DeepObservableCollection(ObservableCollection<T> collection)
: base(collection) {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
public DeepObservableCollection( List<T> collection )
: base( collection ) {
CollectionChanged += DeepObservableCollection_CollectionChanged;
}
void DeepObservableCollection_CollectionChanged( object sender, NotifyCollectionChangedEventArgs e ) {
if ( e.NewItems != null ) {
foreach ( var item in e.NewItems ) {
var notifyPropertyChanged = item as INotifyPropertyChanged;
if ( notifyPropertyChanged != null ) notifyPropertyChanged.PropertyChanged += item_PropertyChanged;
}
}
if ( e.OldItems != null ) {
foreach ( Object item in e.OldItems ) {
var notifyPropertyChanged = item as INotifyPropertyChanged;
if ( notifyPropertyChanged != null ) notifyPropertyChanged.PropertyChanged -= item_PropertyChanged;
}
}
}
void item_PropertyChanged( object sender, PropertyChangedEventArgs e ) {
NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset );
OnCollectionChanged( a );
ItemPropertyChanged?.Invoke( sender, e );
}
}
your UserRoleClass must implement INotifyPropertyChanged (maybe using ObservableObject) but that's all what is required. When you add a new user role datagrid updates through binding.
I have a model class that I wish to bind a combo box to. My plan was to have an object with two propertied. 1) an ObservableCollection that contains the items I want to populate the combo box with. 2) A string property that stores the value of the selected item. I cannot seem to get this to work and open to suggestions. I am Trying to follow MVVM as best as possible. The behavior I observe is an empty combo box.
The class looks like this.
public class WellListGroup : Notifier
{
private ObservableCollection<string> _headers;
public ObservableCollection<string> headers
{
get { return this._headers; }
set { this._headers = value; OnPropertyChanged("headers"); }
}
private string _selected;
public string selected
{
get { return this._selected;}
set { this._selected = value; OnPropertyChanged("selected");}
}
}
Notifier looks like:
public class Notifier : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
And my viewmodel makes a call to a data access layer that creates the following object i wish to bind to.
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
public WellListGroup wlg = new WellListGroup {headers = headers, selected = null};
}
Data Access Layer - getHeaders()
public ObservableCollection<string> getHeaders()
{
ObservableCollection<string> vals = new ObservableCollection<string>();
WVWellModel wvm = new WVWellModel();
var properties = getProperties(wvm);
foreach (var p in properties)
{
string name = p.Name;
vals.Add(name);
}
return vals;
}
Then the view:
<ComboBox DockPanel.Dock="Top" ItemsSource = "{Binding Path = wlg.headers}" SelectedItem="{Binding Path = wlg.selected}"></ComboBox>
View Code Behind (Where the Data Context is set)
public partial class MainView : Window
{
public MainView()
{
InitializeComponent();
MainViewModel mvm = new MainViewModel();
DataContext = mvm;
}
}
App.xaml.cs
public partial class App : Application
{
private void OnStartup(object sender, StartupEventArgs e)
{
Views.MainView view = new Views.MainView();
view.Show();
}
private void APP_DispatcherUnhandledException(object sender,DispatcherUnhandledExceptionEventArgs e)
{
MessageBox.Show(e.Exception.Message);
e.Handled = true;
}
}
I have tried several iterations of this but cant for the life of me get this to work. I am presented with an empty combo box.
I am going to assume DataContext is set to MainViewModel on the view.
I think you well list group should call OnPropertyChanged
public class MainViewModel : Notifier
{
public static getWells gw = new getWells();
public static ObservableCollection<string> headers = gw.getHeaders();
private WellListGroup _wlg = new WellListGroup {headers = headers, selected = null};
public WellListGroup wlg
{
get { return _wlg; }
set { _wlg = value; OnPropertyChanged("wlg"); }
}
The combo box binding should look like this:
<ComboBox
ItemsSource = "{Binding wlg.headers}"
SelectedItem = "{Binding wlg.selected Mode=TwoWay}"
/>
If neither of those work I would make sure the MainViewModel is being instantiated and assigned to DataContext in the Page constructor or a page loaded event.
Here is a code project Tutorial that may help break down the binding process Step by Step WPF Data Binding with Comboboxes
I am developing windows 8 store app. I wants to show the previously selected items in GridView if navigate back and fro, the selected items should be shown selected.I have tried This tutorial
and did exactly as suggested. but its not working in my case. I have also tried with index as
int index = myGridView.SelectedIndex
so that to find index and directly provide
myGridView.SelectedIndex = index ;
but its again not useful because I am not getting changes into the index in
SelectionChanged(object sender, SelectionChangedEventArgs e){};
What works is
myGridView.SelectAll();
it selects all the elements. but I don't want this. Please help me? Thanks in advance
Please refer my code
<GridView x:Name="MyList" HorizontalAlignment="Left" VerticalAlignment="Top" Width="auto" Padding="0" Height="600" Margin="0" ScrollViewer.HorizontalScrollBarVisibility="Disabled" SelectionMode="Multiple" SelectionChanged="names_SelectionChanged" ItemClick="mylist_ItemClick" SelectedItem="{Binding Path=selectedItem}">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Width="260" Height="80">
<TextBlock Text="{Binding Path=Name}" Foreground="White" d:LayoutOverrides="Width" TextWrapping="Wrap"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This is The class I am dealing with
public sealed partial class MyClass: MyApp.Common.LayoutAwarePage, INotifyPropertyChanged
{
SQLite.SQLiteAsyncConnection db;
public MyClass()
{
this.InitializeComponent();
Constants.sourceColl = new ObservableCollection<MyModel>();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
getData();
foreach (MyModel item in Constants.sourceColl)
MyList.SelectedItems.Add(item);
}
private async void getData()
{
List<MyModel> mod = new List<MyModel>();
var query = await db.Table<MyModel>().Where(ch => ch.Id_Manga == StoryNumber).ToListAsync();
foreach (var _name in query)
{
var myModel = new MyModel()
{
Name = _name.Name
};
mod.Add(myModel);
Constants.sourceColl.Add(myModel);
}
MyList.ItemsSource = mod;
}
private void names_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
GridView myGridView = sender as GridView;
if (myGridView == null) return;
Constants.sourceColl = (ObservableCollection<MyModel>)myGridView.SelectedItems;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private MyModel _selectedItem;
public MyModel selectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem != value)
{
_selectedItem = value;
NotifyPropertyChanged("selectedItem");
}
}
}
}
Here is my model
class MyModel
{
[PrimaryKey, AutoIncrement]
public int id { get; set; }
public String Name { get; set; }
}
Hello rahul I have just solved the problem you are facing it is not the perfect way but it will work in your code. try to follow it.
first I made a singleton class which store your previous selected items (lstSubSelectedItems)..like this
public class checkClass
{
static ObservableCollection<Subject> _lstSubSelectedItems = new ObservableCollection<Subject>();
static checkClass chkclss;
public static checkClass GetInstance()
{
if (chkclss == null)
{
chkclss = new checkClass();
}
return chkclss;
}
public ObservableCollection<Subject> lstSubSelectedItems
{
get
{
return _lstSubSelectedItems;
}
set
{
_lstSubSelectedItems = value;
}
}
}
i have filled lstSubSelectedItems on pagenavigationfrom method like this.. here lstsub is selectedsubjects..
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
checkClass obj = checkClass.GetInstance();
obj.lstSubSelectedItems = lstsub;
}
Here is the workaround what I have done in my constructor...
Here I removed the non selected items using removeat function of gridview.selecteditems other function are not doing this this for for (I don't know why). subject class is just like your model class . and also setting of selecteditems is not working that why I choose this way... Hope this help.
public SelectSubject()
{
this.InitializeComponent(); // not required
objselectsubjectViewmodel = new SelectSubjectViewModel(); // not required
groupedItemsViewSource.Source = objselectsubjectViewmodel.Categories; // not required the way set the itemssource of grid.
this.DataContext = this;
checkClass obj = checkClass.GetInstance();
if (obj.lstSubSelectedItems.Count > 0)
{
// List<Subject> sjsfj = new List<Subject>();
// ICollection<Subject> fg = new ICollection<Subject>();
itemGridView.SelectAll();
// int i = 0;
List<int> lstIndex = new List<int>();
foreach (Subject item1 in itemGridView.SelectedItems)
{
foreach (var item3 in obj.lstSubSelectedItems)
{
if (item3.SubjectCategory == item1.SubjectCategory && item3.SubjectName == item1.SubjectName)
{
lstIndex.Add(itemGridView.SelectedItems.IndexOf(item1));
}
}
}
int l = itemGridView.SelectedItems.Count;
for (int b = l-1; b >= 0; b--)
{
if (!lstIndex.Contains(b))
{
itemGridView.SelectedItems.RemoveAt(b);
}
}
}
}
tell me if it works for you...
You can set selectedItems property of gridView for doing this first make observableCollection and the continuously update this collection on selectionchange Event of your gridView . and when you comeback to this page set the GridViewName.SelectedItems = aboveCollection;
private ObservableCollection<Subject> lstsub = new ObservableCollection<Subject>() ;
private void itemGridView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
checkTemp = 1;
GridView tempObjGridView = new GridView();
tempObjGridView = sender as GridView;
lstsub = tempObjGridView.SelectedItems;
}
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
yourGridName.SelectedItems = lstsub ;
}
I want to have AutoCompleteBox to complete addresses by request on server.
I have this method to fill my AutoCompleteBox:
private async void getNearStreets()
{
if (acbAddress.Text.Length > 2)
{
ApiRequest request = new ApiRequest("hintAddress", new HintAddress(appSettings.InstanceId, acbAddress.Text, appSettings.SmsCode));
var postData = JsonConvert.SerializeObject(request);
var response = await HttpHelper.SendRequestGetResponse(postData);
ApiResponseTest apiResponse = (ApiResponseTest)JsonConvert.DeserializeObject<ApiResponseTest>(response);
var wordList = this.Resources["autoCompleteWordList"] as AutoCompleteWordList;
wordList.Clear();
foreach (var adresa in apiResponse.data.result)
{
HintAddressResponse adrResponse = (HintAddressResponse)JsonConvert.DeserializeObject<HintAddressResponse>(adresa.ToString());
wordList.Add(adrResponse.street);
}
}
}
And this is my class:
public class AutoCompleteWordList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<string> _listOfAddresses;
public ObservableCollection<string> ListOfAddresses
{
get { return _listOfAddresses; }
set
{
_listOfAddresses = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ListOfAddresses"));
}
}
public AutoCompleteWordList()
{
ListOfAddresses = new ObservableCollection<string>();
}
public void Add(string address)
{
ListOfAddresses.Add(address);
}
public void Clear()
{
ListOfAddresses.Clear();
}
}
and view:
<phone:PhoneApplicationPage.Resources>
<data:AutoCompleteWordList x:Key="autoCompleteWordList" />
</phone:PhoneApplicationPage.Resources>
<toolkit:AutoCompleteBox x:Name="acbAddress" VerticalAlignment="Top"
ItemsSource="{Binding Source={StaticResource autoCompleteWordList}, Path=ListOfAddresses}"
TextChanged="acbAddress_TextChanged"/>
My problem is that I am downloading data, I added them to collection but DropDownDialog doesn't show up. I think I must alert that I have new data but I don't know how. Thanks for help
When I looked at msdn for first time I missed PopulateComplete method. But It's what I need and when I added at end of my method it works.
I am trying to use the Silverlight 3.0 DataGrid with the MVVM design pattern. My page has a DataGrid and a button that adds an item to the collection in the VM using a command (from the Composite Application Library). This works fine, and the new item is displayed and selected.
The problem I can't solve is how to begin editing the row. I want the new row to be immediately editable when the user clicks the Add button i.e. focus set to the DataGrid and the new row in edit mode.
This is the XAML in the view:
<Grid x:Name="LayoutRoot">
<StackPanel>
<data:DataGrid ItemsSource="{Binding DataView}"/>
<Button cmd:Click.Command="{Binding AddItemCommand}" Content="Add" />
</StackPanel>
</Grid>
The code behind has one line of code that creates an instance of the VM and sets the DataContext of the view.
The VM code is:
public class VM
{
public List<TestData> UnderlyingData { get; set; }
public PagedCollectionView DataView { get; set; }
public ICommand AddItemCommand { get; set; }
public VM()
{
AddItemCommand = new DelegateCommand<object>(o =>
{
DataView.AddNew();
});
UnderlyingData = new List<TestData>();
UnderlyingData.Add(new TestData() { Value = "Test" });
DataView = new PagedCollectionView(UnderlyingData);
}
}
public class TestData
{
public string Value { get; set; }
public TestData()
{
Value = "<new>";
}
public override string ToString()
{
return Value.ToString();
}
}
What would be the best way to solve this problem using the MVVM design pattern?
I faced the same issue. I've introduced interface ISupportEditingState:
public interface ISupportEditingState
{
EditingState EditingState { get; set; }
}
My VM implements it. And then I wrote this behaviour to synchronise editing state of DataGrid and my VM:
public class SynchroniseDataGridEditingStateBehaviour : Behavior<DataGrid>
{
public static readonly DependencyProperty EditingStateBindingProperty =
DependencyProperty.Register("EditingStateBinding", typeof(ISupportEditingState),
typeof(SynchroniseDataGridEditingStateBehaviour), new PropertyMetadata(OnEditingStateBindingPropertyChange));
private bool _attached;
private bool _changingEditingState;
public ISupportEditingState EditingStateBinding
{
get { return (ISupportEditingState)GetValue(EditingStateBindingProperty); }
set { SetValue(EditingStateBindingProperty, value); }
}
private static void OnEditingStateBindingPropertyChange(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var b = d as SynchroniseDataGridEditingStateBehaviour;
if (b == null)
return;
var oldNotifyChanged = e.OldValue as INotifyPropertyChanged;
if (oldNotifyChanged != null)
oldNotifyChanged.PropertyChanged -= b.OnEditingStatePropertyChanged;
var newNotifyChanged = e.NewValue as INotifyPropertyChanged;
if (newNotifyChanged != null)
newNotifyChanged.PropertyChanged += b.OnEditingStatePropertyChanged;
var newEditingStateSource = e.NewValue as ISupportEditingState;
if (newEditingStateSource.EditingState == EditingState.Editing)
{
// todo: mh: decide on this behaviour once again.
// maybe it's better to start editing if selected item is already bound in the DataGrid
newEditingStateSource.EditingState = EditingState.LastCancelled;
}
}
private static readonly string EditingStatePropertyName =
CodeUtils.GetPropertyNameByLambda<ISupportEditingState>(ses => ses.EditingState);
private void OnEditingStatePropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_changingEditingState || !_attached || e.PropertyName != EditingStatePropertyName)
return;
_changingEditingState = true;
var editingStateSource = sender as ISupportEditingState;
if (editingStateSource == null)
return;
var grid = AssociatedObject;
var editingState = editingStateSource.EditingState;
switch (editingState)
{
case EditingState.Editing:
grid.BeginEdit();
break;
case EditingState.LastCancelled:
grid.CancelEdit();
break;
case EditingState.LastCommitted:
grid.CommitEdit();
break;
default:
throw new InvalidOperationException("Provided EditingState is not supported by the behaviour.");
}
_changingEditingState = false;
}
protected override void OnAttached()
{
var grid = AssociatedObject;
grid.BeginningEdit += OnBeginningEdit;
grid.RowEditEnded += OnEditEnded;
_attached = true;
}
protected override void OnDetaching()
{
var grid = AssociatedObject;
grid.BeginningEdit -= OnBeginningEdit;
grid.RowEditEnded -= OnEditEnded;
_attached = false;
}
void OnEditEnded(object sender, DataGridRowEditEndedEventArgs e)
{
if (_changingEditingState)
return;
EditingState editingState;
if (e.EditAction == DataGridEditAction.Commit)
editingState = EditingState.LastCommitted;
else if (e.EditAction == DataGridEditAction.Cancel)
editingState = EditingState.LastCancelled;
else
return; // if DataGridEditAction will ever be extended, this part must be changed
EditingStateBinding.EditingState = editingState;
}
void OnBeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if (_changingEditingState)
return;
EditingStateBinding.EditingState = EditingState.Editing;
}
}
Works ok for me, hope it helps.
Whenever you talk about directly accessing ui components, your kinda missing the point of mvvm. The ui binds to the viewmodel, so find a way to alter the viewmodel instead.