Scenario: I start out in my app's main page. I navigate to sub-page A, change a value, hit the back button and the bound TextBlock in the main page doesn't change. If I navigate to sub-page B, a TextBlock using that same binding changes. Likewise, if I go to page A again I see the changed value. If I exit the app, the new value shows up on the main page. It's just when using the back button that a refresh doesn't get triggered.
I've got all my INotifyPropertyChanged stuff working. Like I said, the binding works in every scenario besides navigating back to the main page. How do I send a message or otherwise trigger a refresh of the bindings on that page? Thanks!
Edit:
Based on the accepted answer from willmel, here's what I did:
My MainPage.xaml file has this markup:
<TextBlock Text="{Binding Title, Mode=OneWay}" />
My MainViewModel.cs file has this:
public string Title
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
}
And I added this to the MainViewModel constructor:
Messenger.Default.Register<PropertyChangedMessage<string>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() => RaisePropertyChanged("Title")));
In another view I have the following markup:
<TextBox Grid.Row="1" Width="250" Height="100" Text="{Binding TitleEdit, Mode=TwoWay}" />
In its view model I use this when getting/setting a string:
public string TitleEdit
{
get { return ProfileModel.Instance.DescriptionProfile.Title; }
set
{
if (ProfileModel.Instance.DescriptionProfile.Title == value) return;
string oldValue = ProfileModel.Instance.DescriptionProfile.Title;
ProfileModel.Instance.DescriptionProfile.Title = value;
RaisePropertyChanged("Title", oldValue, value, true);
}
}
In your view model you want to be modified if a child page changes a property. (note here, the property is of type bool, but could be anything)
Messenger.Default.Register<PropertyChangedMessage<bool>>(this,
(action) => DispatcherHelper.CheckBeginInvokeOnUI(
() =>
{
MessageBox.Show(action.newValue.ToString());
//do what you want here (i.e. RaisePropertyChanged on a value they share)
}));
When you use RaisePropertyChanged in the child class, use the broadcasting overload.
RaisePropertyChanged("Preference", oldValue, value, true);
Finally, note that to use DispatcherHelper, you need to Add the following to your App constructor (App.xaml.cs)
DispatcherHelper.Initialize();
Related
I have a UWP app using MVVM Toolkit. I have a parent ViewModel and a child UserControl.
Everyone says I should pass data from child to parent using Observer pattern. Which is good. MVVM Toolkit provides some classes and methods Send/Receive.
Question
How do I pass data from child user control to parent? The child has 10 text boxes and the parent needs that data after clicking a button 'Create File'. I cannot use Observer Send method after each key press in a text box.
MainPage.xaml
<Grid>...
<uc:MetadataUserControl>
</uc:MetadataUserControl>
<Button Content="Create final"></Button>
</Grid>
MainPageViewModel.cs
private MainPageModel _mainPageModel = null; // Business layer model
public MainPageModel MainPageModel
{
get { return _mainPageModel; }
set { SetProperty(ref _mainPageModel, value); }
}
void Receive() { .. } // Receive data from user controls
MainPageModel.cs
public MetadataModel {get; set;}
MetadataViewModel.cs
private MetadataModel _metadataModel = null; // Business layer model
public MetadataModel MetadataModel
{
get { return _metadataModel; }
set { SetProperty(ref _metadataModel, value); }
}
void Send() {} // Notify parents subscribed to this user control
MetadataUserControl.xaml
<Grid>
<!-- First textbox -->
<TextBlock Text="First textbox"></TextBlock>
<TextBox Text="{x:Bind Path=ViewModel.MetadataModel.FirstText}"></TextBox>
<!-- Second textbox -->
<TextBlock Text="Second textbox"></TextBlock>
<TextBox Text="{x:Bind Path=ViewModel.MetadataModel.SecondText}"></TextBox>
...
</Grid>
SecondMainPage.xaml
<Grid>...
<uc:MetadataUserControl>
</uc:MetadataUserControl>
<Button Content="Create final"></Button>
</Grid>
Call WeakReferenceMessenger.Default.Send(new NotifyParentMessage(new YourInfoClass())); in your Send() method in the child's view model and make sure that the YourInfoClass class contains all information that the parent view model needs about the child, i.e. the values of all properties that are bound to the textboxes in the view.
Managed to do it using Messenger's request feature. When the parent sends an event, the children replies with the object required.
MainViewModel.cs
private void CreateFile()
{
// Request data from children user controls
var metadataRequested = WeakReferenceMessenger.Default.Send<MetadataRequestMessage>();
}
MetadataViewModel.cs constructor
WeakReferenceMessenger.Default.Register<MetadataViewModel, MetadataRequestMessage>(this, (r, m) =>
{
m.Reply(r.MetadataModel);
});
..
public class MetadataRequestMessage : RequestMessage<MetadataModel>
{
}
My application viewModel responds to a user clicking a button to see test results:
private void AddDetailRows(List<QuizHistory> quizHistoryList)
{
quizDetails.Children.Clear();
quizDetails.Children.Add(AddData(quizHistoryList));
quizDetails.Children.Add(new LineTemplate());
}
Where quizDetails is the name of an element in the view.
But this doesn't work for me as the view model doesn't know what the view looks like and does not have access to the names of elements.
In a MVVM application, how is this problem solved?
You are completely right, that is not something that ViewModel is responsible of.
So, whatever you want to do with UI is not responsibility of the ViewModel.
If this is really the only option, then you can think of creating boolean properties in your VM and binding them to your views and then changing that boolean from false to true or vice versa on button click command which is binded to your VM.
To simplify it:
MyView.xaml
<StackLayout>
<Button Command="{Binding ShowHideQuizHistoryCommand}" ... />
<StackLayout x:Name="QuizHistory"
IsVisible={Binding ShowQuizHistory }>
//
</StackLayout>
</StackLayout>
MyViewModel.cs
private bool _showQuizHistory ;
public bool ShowQuizHistory
{
get { return _showQuizHistory ; }
set
{
_showQuizHistory = value;
OnPropertyChanged();
}
}
public ICommand ShowHideQuizHistoryCommand => new Command(() =>
{
ShowQuizHistory = !ShowQuizHistory;
});
So, this is just an example based on what you provided in question.
You can also use visual states, converters, triggers and behaviors in order to achieve this, but in my opinion this is the easiest way.
I have a form where a user can enter some data and save it to a database. I have been trying to add basic validation, making a user have to enter a value in every field. I want my save button to be unavailable until the user has entered some information in every form.
Here is my button:
<ToolbarItem Name="MenuItem1" Order="Primary" Text="Save" Priority="1" Command="{Binding SaveDataCommand}" IsEnabled="{Binding CanSaveData}"/>
I can access the isEnabled property in the XAML but where this gets infuriating is that I can't then reupdate this property. The button gets stuck in whatever state I tell it on load. I have checked my view model and it is returning a boolean which is correct (printing it to the console), it's just there is no way of updating the button state.
I even tried adding a new button that would force update the enabled state however this wouldn't work
<ToolbarItem x:Name="whyxamarinwhy" Name="MenuItem1" Order="Primary" Text="Save" Priority="1" Command="{Binding SaveEvent}" IsEnabled="{Binding CanCreateEvent}"/>
whyxamarinwhy.isenabled = true; //I cannot access this property because it doesn't exist.
Is the only way to implement this functionality going to be using a custom renderer?
MenuItem.IsEnabledProperty / For internal use by the Xamarin.Forms platform.
You should use the Command's CanExecute of the ToolbarItem to determine if the Execute method can be triggered.
Since you are already binding the command to SaveDataCommand, you can toggle the return of the CanExecute of that command to determine if the menu item button should be allowed to trigger the attached command.
You did not post your ViewModel, but in a inner-class ICommand implementation, something as simple as this works:
static bool SaveDataCommandCanExecute = true;
class SaveDataCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
// your code needs to toggle SaveDataCommandCanExecute to determine if the Execute method can be triggered
return SaveDataCommandCanExecute;
}
public void Execute(object parameter)
{
// Do something
}
}
I have a UserControl that has a CheckBox on it. When I consume the UserControl on my main XAML page, I'd like to TwoWay bind a property on the control to a property on my ViewModel e.g.
<myUserControl BtnIsBlacklisted="{Binding IsBlacklisted, Mode=TwoWay}" />
When IsBlacklisted changes, I'd like my checkbox to change too and vice-versa.
Here is what I have,
public static readonly DependencyProperty BtnIsBlacklistedProperty =
DependencyProperty.Register("BtnIsBlacklisted",
typeof(bool),
typeof(MyUserControl),
new PropertyMetadata(false, new
PropertyChangedCallback(BtnIsBlacklistedPropertyChanged))
);
private static void BtnIsBlacklistedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// ... do something here ...
}
public bool BtnIsBlacklisted
{
get { return (bool)GetValue(BtnIsBlacklistedProperty); }
set { SetValue(BtnIsBlacklistedProperty, value); }
}
My UserControl has this for the CheckBox,
<CheckBox x:Name="myCheckBox"
...
IsChecked="{Binding Path=BtnIsBlacklisted,
ElementName=UserControl,
Converter={StaticResource BoolToNotBool},
Mode=TwoWay}" />
The property on my ViewModel object is as follows,
public bool IsBlacklisted
{
get
{
return App.VM.BlacklistedRetailers.Contains(this.Retailer);
}
set
{
if (value)
{
App.VM.BlacklistedRetailers.Add(this.Retailer);
}
else
{
while (App.VM.BlacklistedRetailers.Contains(this.Retailer))
{
App.VM.BlacklistedRetailers.Remove(this.Retailer);
}
}
this.NotifyPropertyChanged("IsBlacklisted");
}
}
The only way BlacklistedRetailers changes is through the set method above so there is no need to trigger a NotifyPropertyChanged from there ...
I have tried many of the suggestions in other questions i.e.
using a dependency property
including Mode=TwoWay
Binding on the UserControl using a self-referencing DataContext set on the containing grid (this does not work either).
however none of these have worked.
Some final notes:
This is for a Windows Phone 7.5 project
Edit: One way binding doe not work either, it seems it there is a problem binding to the UserControl's own properties
An ElementName Binding matches against x:Name values which are in the same name scope as the element on which the binding is being set. There's not enough of the code shown to tell but you're using "UserControl" which I'm guessing is not set as the name of the element, but is being used to try and match the type. The ElementName also might not be able to resolve if the CheckBox is declared inside a template.
I have a command wired to the event such that it does fire, but what I get in the CommandParameter is the previously selected item, or maybe it's the selected item before the SelectionChanged completes.
Either way, not sure what to change to get the newly selected item from the event.
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventToCommand
Command="{Binding Main.SelectedRecordCommand, Source={StaticResource Locator}}"
CommandParameter="{Binding SelectedItem, ElementName=listBillingRecords}"
/>
</i:EventTrigger>
</i:Interaction.Triggers>
Thanks
Is it worth using a trigger? If whatever your XAML element is for the collection (listbox, grid, etc) is bound to a property exposing a collection on your viewmodel, you can leverage both databinding and the built-in MVVM Light messenger to notify you of a property change with both old and new values in a more MVVM-friendly way. This example isn't necessarily WP7-specific, but I think it would work the same.
For example, this might be the databound collection:
public const string BillingRecordResultsPropertyName = "BillingRecordResults";
private ObservableCollection<BillingRecord> _billingRecordResults = null;
public ObservableCollection<BillingRecord> BillingRecordResults
{
get
{
return _billingRecordResults;
}
set
{
if (_billingRecordResults == value)
{
return;
}
var oldValue = _billingRecordResults;
_billingRecordResults = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(BillingRecordResultsPropertyName, oldValue, value, true);
}
}
I like to expose a property on my ViewModel that is a "selected item" of whatever collection I'm exposing. So, to the ViewModel, I would add this property using the MVVMINPC snippet:
public const string SelectedBillingRecordPropertyName = "SelectedBillingRecord";
private BillingRecord _selectedBillingRecord = null;
public BillingRecord SelectedBillingRecord
{
get
{
return _selectedBillingRecord;
}
set
{
if (_selectedBillingRecord == value)
{
return;
}
var oldValue = _selectedBillingRecord;
_selectedBillingRecord = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(SelectedBillingRecordPropertyName, oldValue, value, true);
}
}
Now, if you bind the SelectedItem of the XAML element to this exposed property, it will populate when selected in the View via databinding.
But even better, when you leverage the snippet MVVMINPC, you get to choose whether or not to broadcast the results to anyone listening. In this case, we want to know when the SelectedBillingRecord property changes. So, you can have this in the constructor for your ViewModel:
Messenger.Default.Register<PropertyChangedMessage<BillingRecord>>(this, br => SelectedRecordChanged(br.NewValue));
And elsewhere in your ViewModel, whatever action you want to have happen:
private void SelectedRecordChanged(BillingRecord br)
{
//Take some action here
}
Hope this helps...
I have seen the same issue and found that SelectedItem is the correct implementation.