I am working with WPF DataGrid in the MVVM manner and having trouble reverting the selection change from the ViewModel.
Is there any proven way to do this? My latest tried out code is below. Now I do not even mind investing on a hack inside the code behind.
public SearchResult SelectedSearchResult
{
get { return _selectedSearchResult; }
set
{
if (value != _selectedSearchResult)
{
var originalValue = _selectedSearchResult != null ? _selectedSearchResult.Copy() : null;
_selectedSearchResult = value;
if (!DispatchSelectionChange(value)) // Returns false if the selection has to be cancelled.
{
_selectedSearchResult = originalValue;
// Invokes the property change asynchronously to revert the selection.
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() => NotifyOfPropertyChange(() => SelectedSearchResult)));
return;
}
NotifyOfPropertyChange(() => SelectedSearchResult);
}
}
}
After days of trial and error, finally got it working. Following is the code:
public ActorSearchResultDto SelectedSearchResult
{
get { return _selectedSearchResult; }
set
{
if (value != _selectedSearchResult)
{
var originalSelectionId = _selectedSearchResult != null ? _selectedSearchResult.Id : 0;
_selectedSearchResult = value;
if (!DispatchSelectionChange(value)) // Returns false if the selection has to be cancelled.
{
// Invokes the property change asynchronously to revert the selection.
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() => RevertSelection(originalSelectionId)));
return;
}
NotifyOfPropertyChange(() => SelectedSearchResult);
}
}
}
private void RevertSelection(int originalSelectionId)
{
_selectedSearchResult = SearchResults.FirstOrDefault(s => s.Id == originalSelectionId);
NotifyOfPropertyChange(() => SelectedSearchResult);
}
Key here is to use a brand new originally selected item from the databound grid's collection (ie: SearchResults) rather than using a copy of the selected item. It looks obvious, but took days for me to figure it out! Thanks for everyone who helped :)
if you want to prevent the selection change you can try this.
if you want to revert a selection, you can just use ICollectionView.MoveCurrentTo() methods (at least you must have to know what item you want to select ;)).
its not quite clear to me what you really want.
Related
I have datagridview on winform. It is bound to result from below code:
PoolEntities db = new PoolEntities();
var Result = db.General_Pool_Detail.Where(g => g.Pool_Name == cbxGLType.SelectedValue && g.Mapped_Date == dt).Select(s=>
new { Selected = true, s.Gen_Pool_ID, s.GSL_Code, s.Amount }).ToList();
dgvGeneralPoolData.DataSource = Result;
The code works perfectly fine. But when I uncheck the checkbox on datagridview it does not work.
In datagirdview event i have written the following code:
private void dgvGeneralPoolData_CellClick(object sender, DataGridViewCellEventArgs e)
{
if (dgvGeneralPoolData.Rows[e.RowIndex].Cells[0].Selected)
{
Boolean IsChecked = (Boolean) dgvGeneralPoolData.Rows[e.RowIndex].Cells[0].Value;
if (IsChecked)
{
dgvGeneralPoolData.Rows[e.RowIndex].Cells[0].Value = false;
}
}
}
Also, I want to save the changes made in datagridview to the database.
Please help.
EditMode Property
check editmode property of your datagridview.
Does it even hit your click event? Where exactly does it break when you debug? Try to debug and provide some more info if you can.
1) Debug and make sure that the event handler does work. Sometimes when you copy paste the code the event handler doesn't get register by the designer so try deleting the CellClick and type the code again on the grid view, when you add the = sign it should give you an option to create the ClickCell method for you. It should be something like this inside your click event.
if (e.RowIndex != -1)
{
DataGridViewCheckBoxCell chk = (DataGridViewCheckBoxCell)dgvGeneralPoolData.CurrentRow.Cells["ColumnNumberHere"];
if (chk.Value == null || chk.Value = false)
{
chk.Value = true;
}
else
{
chk.Value = false
}
}
2) Make sure this is under your InitializeComponent()
this.dgvGeneralPoolData.CellContentClick += new System.Windows.Forms.DataGridViewCellEventHandler(this.dgvGeneralPoolData_CellContentClick);
The CurrentSelectedBall is updated whenever I changed its value due to two-way binding.
When I click to update the function UpdateRedBall is called so the redball in the database is updated. But the view list of balls is not updated as the ObservableCollection<RedBall> RedBalls is not changed at all.
How to fix this problem? I guess something needs to be done after _context.SaveChanges();
Also I can not simply do DataGridA.itemsSource = RedBalls to make a hard refresh here as first DataGridA is not accessible in the MainviewModel.
Some of the methods:
public ObservableCollection<RedBall> RedBalls
{
get { return new ObservableCollection<RedBall>(_context.RedBalls.OrderBy(m=>m.DateNumber));}
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
//RedBalls = RedBalls
}
I have a strong dislike for getters that 'do stuff'. It should be impossible for a get to fail. I would consider moving the logic in your RedBalls getter elsewhere. I'm also concerned that you're newing up the RedBalls ObservableCollection on every get as it can cause problems with bindings. Bound ObservableCollections should be instantiated once, then altered using Add, Remove and Clear. Working with them this way should also solve the problem of updating your collection appropriately.
public ObservableCollection<RedBall> RedBalls
{
get { return _redBalls; }
set
{
_redBalls = value;
RaisePropertyChanged("RedBalls");
}
}
public RedBall CurrentSelectedRedBall
{
get { return _currentSelectedRedBall; }
set
{
_currentSelectedRedBall = value;
RaisePropertyChanged("CurrentSelectedRedBall");
}
}
private void UpdateCurrentSelectedRedBall()
{
UpdateRedBall();
var redBalls = _context.RedBalls.OrderBy(m=>m.DateNumber);
if(redBalls != null)
{
RedBalls.Clear();
foreach(RedBall rb in redBalls)
{
RedBalls.Add(rb);
}
}
}
private void UpdateRedBall()
{
if (CurrentSelectedRedBall != null)
{
var ballToUpdate = _context.RedBalls.SingleOrDefault(x => x.Id == CurrentSelectedRedBall.Id);
ballToUpdate.DateNumber = CurrentSelectedRedBall.DateNumber;
ballToUpdate.First = CurrentSelectedRedBall.First;
ballToUpdate.Second = CurrentSelectedRedBall.Second;
ballToUpdate.Third = CurrentSelectedRedBall.Third;
ballToUpdate.Fourth = CurrentSelectedRedBall.Fourth;
ballToUpdate.Fifth = CurrentSelectedRedBall.Fifth;
ballToUpdate.Sixth = CurrentSelectedRedBall.Sixth;
}
_context.SaveChanges();
}
Use something like this: https://gist.github.com/itajaja/7507120 - basically you need to subscribe for PropertyChanged and raise CollectionChanged events from that.
I am having a problem with getting data from db and showing in UI asynchronously.
I am using MVVM light, when I click the button, action is triggered in ViewModel:
private void SearchQuery(string query)
{
_redisModel.GetFriendsListAsync(query);
}
At some point GetFriendsListCompleted is called by background thread notifing viewmodel that job is done.
At this point I need to update ListBox ItemSource. But when I try to update is I get
“The calling thread cannot access this object because a different thread owns it”
I have tried Dispatcher.CurrentDispatcher.Invoke(),App.Current.Dispatcher.Invoke() and different magic, but it still doesn’t work.
I tried to give UI dispatcher to ViewModel and then call it from there - didn't work.
private string filterText = string.Empty;
public string FilterText
{
get { return filterText; }
set
{
filterText = value;
this.RaisePropertyChanged(() => this.FilterText);
this.FriendsList.View.Refresh(); // Here where exception is happening.
}
}
I tried to change this line to
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>this.FriendsList.View.Refresh())); - still the same.
I am using Telerik ListBox to display items. FriendList is CollectionViewSource(http://www.telerik.com/help/wpf/radlistbox-overview.html). It works when I use Telerik example from WPF Control Examples. Problems start to occur when I use my async methods.
Type of view is System.ComponentModel.ICollectionView it is used for Filtering and Grouping.
I have also tried to just assign ObservableCollection to Items property of the ListBox and it doesn't work either.
A bit more details on how _redisModel.GetFriendsListAsync works:
In the end(after all chain of calls) it ends up here:
public GetAsyncResult(Func<T> workToBeDone, Action<IAsyncResult> cbMethod, Object state)
{
_cbMethod = cbMethod;
_state = state;
QueueWorkOnThreadPool(workToBeDone);
}
ThreadPool.QueueUserWorkItem(state =>
{
try
{
_result = workToBeDone();
}
catch (Exception ex)
{
_exception = ex;
}
finally
{
UpdateStatusToComplete(); //1 and 2
NotifyCallbackWhenAvailable(); //3 callback invocation
}
});
In viewmodel I have method:
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => this.FriendsList.View.Refresh()));
}
}
Can anybody please help me with this ?
Thank you
You are creating CollectionViewSource in one thread and refreshing that in another thread (dispatcher thread). Update your GetFriendsListCompleted to
private void GetFriendsListCompleted(object sender, ResultsArgs<Friend> e)
{
if (!e.HasError)
{
var curr = e.Results;
if (curr != null)
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() => {
this.FriendsList= new CollectionViewSource();
this.FriendsList.Source = list;
this.FriendsList.Filter += this.FriendFilter;
FilterText = "";
this.FriendsList.View.Refresh();
}));
}
}
}
You haven't shown any of the code that's actually running on the background thread on completion but I'm guessing that in it you're creating a collection object that you're then trying to assign to your CollectionView. When the CV tries to update (on the UI thread) from your Refresh call it would then try to use the collection that's owned by the other thread.
If you include the relevant code it would be easier to say for sure.
I have a combobox of type List. I have the ItemsSource and the ItemSelected bound through the datacontext. If the selected item has been changed then I show a pop up message confirming the users action. On clicking of 'Ok' the selection gets changed. But on clicking of cancel, the selection should be cancelled and previous item should be retained. Below is the property that is bound to SelectedItem of the combobox.
Public SomeClass Sel
{
get
{
return _sel;
}
set
{
if (_sel != value)
{
var sview = _sel;
if (Compare())
{
_sel = value;
if (Sel != null)
IsDefault = Sel.IsDefault;
OnPropertyChanged(() => Sel);
}
else
{
MessageBoxResult result = MessageBox.Show("Message.", "Owb Message", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
_sel = value;
if (Sel != null)
IsDefault = Sel.IsDefault;
OnPropertyChanged(() => Sel);
}
else
{
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
_sel = sview;
OnPropertyChanged("Sel");
}), DispatcherPriority.Send, null);
return;
}
}
}
}
}
The combo box is in a pop window. So would Dispatcher object work in that case?
I'm guessing the selected value is retained, but the View doesn't update correctly.
Have a look at this article: http://www.codeproject.com/Articles/407550/The-Perils-of-Canceling-WPF-ComboBox-Selection. Basically, the few workarounds that did exist in .Net 3.5 no longer work in .Net 4.0..
As a general rule, if you've got visual controls leaking into your viewmodel, you're going down a path you don't want to go down.
Create a Behavior that intercepts the OnChanged event of the ComboBox and launches a message box. Here's a tutorial on using behaviours
This keeps all the UI logic in the UI and leaves your viewmodel to manage data and validation.
It works like magic now! I missed out setting the value before calling dispatcher.
_sel = sview
Using this Expandable List checkbox example code as an baseline, I am trying to save and maintain the checkbox state. However, random checkboxes are being checked and unchecked ( triggering my OnCheckedChangeListener with the new values ) when I scroll them out of sight, minimize their group, or even minimize/maximize a nearby group!
public Object getChild(int groupPosition, int childPosition) {
return colors.get( groupPosition ).get( childPosition );
}
public long getChildId(int groupPosition, int childPosition) {
return (long)( groupPosition*1024+childPosition ); // Max 1024 children per group
}
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
View v = null;
if( convertView != null ) {
v = convertView;
} else {
v = inflater.inflate(R.layout.child_row, parent, false);
}
Color c = (Color)getChild( groupPosition, childPosition );
TextView color = (TextView)v.findViewById( R.id.childname );
if( color != null ) {
color.setText( c.getColor() );
}
TextView rgb = (TextView)v.findViewById( R.id.rgb );
if( rgb != null ) {
rgb.setText( c.getRgb() );
}
CheckBox cb = (CheckBox)v.findViewById( R.id.check1 );
cb.setChecked( c.getState() );
cb.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
colors.get(groupPosition).get(childPosition).setState(isChecked);
context.setColorBool(groupPosition, childPosition, isChecked);
Log.d("ElistCBox2", "listitem position: " +groupPosition+"/"+childPosition+" "+isChecked);
}
});
return v;
}
I don't know what piece of code could be responsible for this, so any suggestions on what to include here are welcome. My code only differs from the original in my attempt to save the values.
my guess is that as your adapter is creating views, the check listener is being called as the checkbox view is initialized. a lot of widgets in android work like this ... the listener is called when the view is initialized.
i don't know why things work like this, but it might be to allow the client code to initialize itself in a consistent way. e.g., whether the checkbox is checked by the user or whether it is initialized as checked, run the same code.
to counteract this, you can try doing something like setting a flag in your listener class impl to allow you to ignore the first click, something like,
cb.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
private void first = true;
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
if (first) {
first = false;
return;
}
colors.get(groupPosition).get(childPosition).setState(isChecked);
context.setColorBool(groupPosition, childPosition, isChecked);
Log.d("ElistCBox2", "listitem position: " +groupPosition+"/"+childPosition+" "+isChecked);
}
});
also, ensure that you are correctly re-using convertView in your implementation of getView() in your adapter. e.g.,
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if (view == null) {
view = inflater.inflate(R.layout.applications_item, null);
}
This is a very old question, but I struggled with the same problem so here is my answer for anybody looking:
The simplest way is to use CheckBox.onClickListener instead of onCheckedChangeListener.
This is only mildly annoying in terms of rearranging your logic, but will ensure that when the boxes are unchecked randomly (by, e.g. expanding an adjacent group) the event will not fire.
Honestly I think this should be considered a bug, even though I'm sure the behaviour can be explained from the Android source.