Cancel a user edit from property set in the ViewModel? - wpf

quite a newbie question, i'm sure, but i wasn't able to find an answer...
I have a control (in this case- a combo box) which is bound to a ViewModel property:
<ComboBox
x:Name="methodTypeCmb"
Grid.Row="0" Grid.Column="2"
ItemsSource="{Binding Path=AllNames, Mode=OneTime}"
SelectedItem="{Binding Path=Name, ValidatesOnDataErrors=True, Mode=TwoWay}"
Validation.ErrorTemplate="{x:Null}"
/>
In my ViewModel, when this property changes, I want to ask the user to confirm the change.
If the user clicks 'no', I want to cancel the change.
However, I must be doing something wrong, because my view doesn't revert back to the previous value when the change is cancelled.
The ViewModel's property:
public string Name
{
get { return m_model.Name; }
set
{
if (MessageBox.Show("Are you absolutely sure?","Change ",MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// change name
}
base.OnPropertyChanged("Name");
}
}

Because you are cancelling within the scope of the text changing event, wpf ignores the property changed event. You must call it from the dispatcher
Dispatcher.CurrentDispatcher.BeginInvoke((ThreadStart)delegate
{
OnPropertyChanged("Name");
});
You should leave your existing "OnPropertyChanged("Name");" at the bottom of the function just add the above line to the block where you are cancelling
EDIT: The following code works I have tested it
public string Newtext
{
get
{
return this._newtext;
}
set
{
if (MessageBox.Show("Apply?", "", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
this._newtext = value;
this.OnPropertyChanged("Newtext"); //Ignored
}
else
{
Dispatcher.CurrentDispatcher.Invoke((ThreadStart)delegate
{
OnPropertyChanged("Newtext");
});
}
}
}

Related

MVVM OnpropertyChange UI changes delayed

i have a comboxbox that while it is beign populated i want it replaced in the UI by a message saying it is being loaded.
i did this by using a textbox showing the message and giving both objects visibility bindings in the view model (IsShowAuthComboBox &LoadingAuthenticationMsg)
here's the XAML code
<ComboBox x:Name="ComboBoxAuthSource"
Grid.Row="3"
Style="{StaticResource ComboBoxStyle}"
SelectedItem ="{Binding SelectedAuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding AuthenticationSource,UpdateSourceTrigger=PropertyChanged}"
Visibility= "{Binding IsShowAuthComboBox, Converter={StaticResource BoolToVis}}" />
<TextBox x:Name="ComboBoxAuthCover"
Grid.Row="3" Grid.Column="{StaticResource TableColumn}"
Style="{StaticResource FieldBoxStyle }"
FontSize="12"
IsReadOnly="True"
Visibility="{Binding IsShowGettingAuthenticationMsg, Converter={StaticResource BoolToVis}}"
Text="{Binding LoadingAuthenticationMsg,UpdateSourceTrigger=PropertyChanged,Mode=OneWay,FallbackValue='Loading authentication sources...'}" />
And here's the viewModel
public bool IsShowAuthComboBox
{
set
{
if (_isShowAuthenticationComboBox != value)
{
_isShowAuthenticationComboBox = value;
OnPropertyChanged("IsShowAuthComboBox");
OnPropertyChanged("IsShowGettingAuthenticationMsg");
}
}
get =>_isShowAuthenticationComboBox;
}
public bool IsShowGettingAuthenticationMsg => !_isShowAuthenticationComboBox;
public virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
Log.Write(LogClass.General, LogLevel.Debug,
$"{propertyName} update triggerd",
_moduleName);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
this code is the first thing that happens in the relevant flow, but i will sometimes only see it at the very end of the execution and for only for an instant.
at other times it will work as expected.
what am i missing here?
EDIT :
this also accurs when validating the IP ,simpler code.
here's the code
public string SelectedServer
{
get => _selectedServer;
set
{
lock (_lockObj)
{
IsShowAuthComboBox = false;
if (!IsValideIp(value))
//some code
IsShowAuthComboBox = true;
}
}
bool IsValideIp(string ip)
{
//some code
//calls the server sync
return RemotingConfigurator.GetServerConfig(ip).isValid;
}
Your issue is that you are setting the IsShowAuthComboBox property and calling the IsValideIp synchronously on the same thread. And a single thread cannot both update the UI and query a database simultaneously.
What you should do is to call the IsValideIp on a background thread. I wouldn't do this in the setter of a property though, but rather in a command. You may want to read #Stephen Cleary's blog post on the subject.
this is what i ended up doing. moved the UI changes away from the data layer and into the viewModel (SetUiOnWait)
public string SelectedServer
{
get => _selectedServer;
set
{
//IsShowAuthComboBox = false;
SetUiOnWait(true);
Log.Write(LogClass.General, LogLevel.Debug,
$"Server changed from {_selectedServer} to {value} by user",
_moduleName);
_selectedServer = value;
OnPropertyChanged();
// OnPropertyChanged();
//workaround for when changing servers when a unique
//authentication source is selected causes the selected source to be null :\
if (AuthenticationSource.Any())
{
SelectedAuthenticationSource = AuthenticationSource[0];
}
Task.Factory.StartNew(() =>
{
LoginInfo.SelectedServer = _selectedServer;
}).ContinueWith((t) =>
{
if(t.Exception !=null)
{
ExceptionLog.Write(t.Exception.GetBaseException(),_moduleName);
}
RefreshAuthenticationProperties();
OnPropertyChanged("IsLimitedClinicalUse");
OnPropertyChanged("IsNotForClinicalUse");
SetUiOnWait(false);
});
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
dispatcher.Invoke((Action)(() =>
{
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}));
}
Task.Factory.StartNew() forces and logic to be executed on a new thread and for the UI changes to wait for it be completed.
and invoke within OnPropertyChange forces the event to be handled by the UI thread.

WPF listbox removing item properly

Hi I am making an WPF application and have a problem with a listbox/listview, MVVM is implemented. I am creating a list of a class that is displayed on the listbox and I am editing the items through selecting an item in the listbox. The problem is when I am deleting an item it doesn't trigger onpropertychanged event to the UI, but is however working in the code, the values are right. When I close the window and reopens it then the list is updated, but not directly when the item is deleted, it never triggers onpropertychanged event for some reason.
It does work to just filter the quicknotelist like
quicknotelist = quicknotelist.where(x => x.id != selecteditem.id);
It works only once though and the UI updates however the selecteditem doesn't seem to work properly even though I am declaring
selecteditem = new quicknote() {*values*};
Part of relevant code, I am using INotifyPropertyChanged
private QuickNote selectedNote = new QuickNote(); // weeeeeee
public QuickNote SelectedNote
{
get
{
return selectedNote;
}
set
{
if (SelectedNote != null)
{
selectedNote = value;
OnPropertyChanged("SelectedNote");
EnableEditNoteBox = true;
}
}
}
private List<QuickNote> quickNoteList = new List<QuickNote>();
public List<QuickNote> QuickNoteList
{
get { return quickNoteList; }
set { quickNoteList = value; OnPropertyChanged("QuickNoteList"); }
}
here is the method that deletes the item
private void DeleteNote(object obj)
{
if (SelectedNote != null)
{
QuickNoteList.Remove(SelectedNote);
// I want this to trigger onpropertychanged without using myclasslist = newclasslist; since it messes up selecteditem to null.
}
}
heres the xaml part.
<ListBox
Width="713"
Height="230"
SelectedItem="{Binding SelectedNote, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding QuickNoteList,BindsDirectlyToSource=True, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="Notes"
Foreground="Black"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.CanContentScroll="False"/>
I'd leave a comment if I could. You should lookup ObservableCollection. I think QuickNoteList should be of this type.

Re-evaluate a ListView's SelectedItem after changing ItemSource

I have a ListView which I would like te re-evaluate its SelectedItem once it receives a new ItemSource. The goal of this is to 'remember' if the user already selected an item in the ListView.
XAML:
<ListView
x:Name="_matchingTvShowsFromOnlineDatabaseListView"
Grid.Row="0"
Grid.Column="0"
Grid.RowSpan="3"
ItemsSource="{Binding AvailableMatchingTvShows}"
SelectedItem="{Binding AcceptedMatchingTvShow, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The SelectedItem is also bound to a property on my VM.
The VM:
public IWebApiTvShow AcceptedMatchingTvShow
{
get
{
IWebApiTvShow acceptedTvShow = null;
if (FoundTvShows.Count > 0)
{
var tvShowName = FoundTvShows[CurrentTvShow];
acceptedTvShow = AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
}
return acceptedTvShow;
}
set
{
if (value != null)
{
var tvShowName = FoundTvShows[CurrentTvShow];
var currentlyAcceptedTvShow =
AvailableTvShowMatches[tvShowName].FirstOrDefault(webApiTvShow => webApiTvShow.Accepted);
if (currentlyAcceptedTvShow != null)
{
currentlyAcceptedTvShow.Accepted = false;
}
value.Accepted = true;
}
OnPropertyChanged();
}
}
I made a screen shot of the application I am building, which hopefully makes clear what I am trying to achieve.
The idea would be that when the user is navigating through the TV Shows, the application would remember the associated TV Show.
Currently, when I associate a TV Show, and navigate to the next TV Show and back again, nothing is selected (the getter of the property AcceptedMatchingTvShow is not executed after setting the new ItemSource)
UPDATE:
Added the code for AvailableMatchingTvShows
private ObservableCollection<IWebApiTvShow> _availableMatchingTvShows;
public ObservableCollection<IWebApiTvShow> AvailableMatchingTvShows
{
get { return _availableMatchingTvShows; }
set
{
_availableMatchingTvShows = value;
OnPropertyChanged("AcceptedMatchingTvShow");
}
}
Without seeing all of your ViewModel, I'm guessing if you raise PropertyChanged("AcceptedMatchingTvShow") when the ItemsSource binding changes that would update the SelectedItem binding.

How to bind tooltip to a button for different conditions

How to bind tooltip dynamically for different conditions
we have 2 Projects in the solution v are using PRISM framework
GeneralBL contains the business logic and
StudentManagementUI contains the Usercontrols ,views and ViewModels
Have StudentStatusUserControl.xaml.cs contains a Telerik RadButton
<telerik:RadButton Name="button1" Content="Stauses" Height="24" HorizontalAlignment="Left" VerticalAlignment="Top" Width="112" FontSize="12" Margin="2,2,2,2"
prism:Click.Command="{Binding ButtonstatusCommand}">
this is enabled for a specific condition & when it is disabled we have to show the mouse hover or tooltip info depending on the condition
In the StudentStatusViewModel.cs
private bool CanExecuteButtonStatusCommand(object o)
{
return SharedLogicBL.CanExecuteButtonStatusCommand(controller,dataService, _selectedItem);
}
SharedLogicBL.cs in GeneralBL project
public static bool CanExecuteUnplannedInspection(BaseController controller, DataService dataService, SDataItem selectedItem)
{
if(controller.currentuser.Isallowed())
{
if(selectedItem!=null)
{
Orders = dataservice.GetOrders(selectedItem);
return !Orders.Any();
}
}
else
return false;
}
In the above method check to see if the user has the rights, if not Tooltip on the button "User doesn't have the rights"
Let first condition is true , in the Orders.Any() returns false then we should display "the selected student has no orders"
Also have a dependency property in the StudentStatusUserControl.xaml.cs for this StudentStatusUserControlBL in the GeneralBL project
Create a public property in your viewmodel that you can databind the telerik button tooltip text to.
public string Button1TooltipText
{
get {
if (!controller.currentuser.Isallowed())
{ return "User doesn't have the rights" }
else
{
if (!SharedLogicBL.CanExecuteButtonStatusCommand(controller, dataService, _selectedItem))
return "the selected student has no orders";
else
return "Execute the unplanned inspection";
}
}
}
Since this property depends on the currently selected item, you'll need to call NotifyPropertyChanged("Button1TooltipText") when _selectedItem changes.

Binding view with model (view doesn't update)

i'm implementing something, that if i select something in my listbox, some textboxes come visible. So i can fill in some details of the selected item. I already implemented a visibilityconverter and this is my code of xaml and viewmodel:
The items in the listbox are objects of class Question
public Question SelectedQuestionDropList
{
get { return selectedQuestionDrop; }
set
{
selectedQuestionDrop = value;
OnPropertyChanged("SelectedQuestionDropList");
Visible = true;
}
}
this is my property of Visibility:
public Boolean Visible
{
get { return visible; }
set { visible = value; }
}
my xaml looks like this:
<ListBox SelectedItem="{Binding Path=SelectedQuestionDropList, UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
DisplayMemberPath="Description"
/>
<TextBox Height="23" Visibility="{Binding Path=Visible, Converter={StaticResource boolToVis},UpdateSourceTrigger=PropertyChanged,Mode}" />
But i have a problem, when i select something, the property visible is set to true, but the visibility of the textbox stays false .. so my view doesn't update with the viewmodel.
someone who knows what i am doing wrong?
In order for the Visibility Binding to update you have to change your property to call OnPropertyChanged:
public Boolean Visible
{
get { return visible; }
set
{
visible = value;
OnPropertyChanged("Visible");
}
}

Resources