my problem is that I want to call the 'SelectionChanged' Event in my ViewModel.
I have a ComboBox (here called ListPicker, it's a phone application):
<tool:ListPicker Name="txt_LZZ"
ItemsSource="{Binding ZZR}" SelectedItem="{Binding MySelectedItem}" />
My Property in the ViewModel looks like this:
private List<string> _zzr;
public List<string> ZZR
{
get
{
_zzr = new List<string>();
_zzr.Add("Jahr");
_zzr.Add("Monat");
_zzr.Add("Woche");
_zzr.Add("Tag");
return _zzr;
}
set
{
_zzr = value;
RaisePropertyChanged(() => ZZR);
}
}
private string _mySelectedItem;
public string MySelectedItem
{
get
{
return _mySelectedItem;
}
set
{
if (value == _mySelectedItem)
return;
_mySelectedItem = value;
RaisePropertyChanged(() => MySelectedItem);
GetValues();
}
}
The program only calls the get method once, while _mySelectedItem has the value 'null'. What I want is that when I change the SelectedItem in my Combobox (ListPicker), the ViewModel has to call the method GetValues, which is in my setter for MySelectedItem. Problem: -> ViewModel doesn't call setter. Why?
Try setting the mode of the binding to TwoWay:
<tool:ListPicker Name="txt_LZZ"
ItemsSource="{Binding ZZR}" SelectedItem="{Binding MySelectedItem, Mode=TwoWay}" />
I also wouldn't instantiate your value collection in the getter, but instead make the setter private, and assign a value to the property in your view model (e.g. the constructor or when the view model is activated).
Related
I have moved xamrin.form.maps to MVVM and have set my xaml to
<viewModels:CustomMap IsShowingUser="True" x:Name="customMap" MapPosition="{Binding MyPosition}" CustomPins="{Binding PinCollection}" />
My customMap Class
public static readonly BindableProperty MapPositionProperty = BindableProperty.Create(
nameof(MapPosition),
typeof(Position),
typeof(CustomMap),
new Position(0, 0),
propertyChanged: (b, o, n) =>
{
Console.WriteLine("here");
((CustomMap)b).MoveToRegion(MapSpan.FromCenterAndRadius(
(Position)n, Distance.FromMiles(.05)));
});
public Position MapPosition
{
get { return (Position)GetValue(MapPositionProperty); }
set { SetValue(MapPositionProperty, value); }
}
From page one, I am sending a lat lng to the map. If I set MapPosition in my constructor in my VM it works perfect, the problem is getting the lat and lng to my constructor before everything is bound, other wise it will not update.
In the view model I am collecting the parameters to set the position.
public override async void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("mapLocation"))
{
var mapLocation = parameters.GetValue<MapLocation>("mapLocation");
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
}
}
This has nothing to do with the time when your property is updated.
You bound MapPosition to MyPosition, which I'd guess is a property. For MVVM to work, this MapPosition has to notify that it has changed, usually this looks something like (the the documentation of INotifyPropertyChanged.PropertyChanged)
public MapLocation MyPosition
{
get => myPosition;
set
{
if(myPosition == value)
{
return;
}
myPosition = value;
OnPropertyChanged();
}
}
private void OnPropertyChanged([CallerMemberName] string memberName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Your view does know that if the bound objects implements INotifyPropertyChanged it has to subscribe to PropertyChanged and can update its state accordingly if this event is raised.
Now why doesn't it work in your case?
You are not setting MyPosition, allowing the setter to raise PropertyChanged and thus the view to update its state, but you are directly setting the backing field myPosition. In this case, the setter never is called and the view has no chance to know that the value of MyPosition has changed, therefor won't update its state.
If your setter implements the pattern shown above, just change
myPosition = new Position(mapLocation.Lat, mapLocation.Lng);
to
MyPosition = new Position(mapLocation.Lat, mapLocation.Lng);
and it should work. Otherwise implement the change notification pattern in MyPosition, too.
I am trying to learn MVVVM, by doing a weather application, using a Udemy course as a reference, the Bing Maps Api, and the OpenWeather API.
I am trying to bind the city with the text of the AutoSuggestBox of my xaml
I have done the WeatherVM and bind it to the view, as a page resource
I also did a quick Method that gets me the City and the Country code, where I am (Using the Bing Maps api)
I Called the method from the MainPage.cs, only to see if it works, and it work fine I get the City and Country Code as expected
WeatherVM
public OpenWeather OpenWeather { get; set; }
private Task<string> _city;
public Task<string> city {
get { return _city; }
set {
_city = value;
GetLocationData();
}
}
public WeatherVM() {
OpenWeather = new OpenWeather();
}
private async void GetLocationData() {
var cityData = await MapLocator.GetCityData();
}
}
}
MainPage.xaml
x:Class="MVVM_Example.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MVVM_Example"
xmlns:vm="using:MVVM_Example.ViewModel"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Resources>
<vm:WeatherVM x:Key="vm" />
</Page.Resources>
<Grid DataContext="{StaticResource vm}">
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Source={StaticResource vm}, Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Page>
I expect "Orlando" to appear in my AutoSuggestBox
When you use Binding, you need to set the DataContext of the page to be an instance of your binding source class instead of set the page.Resource. For more details, you can refer to this document.
<Page.DataContext>
<local:WeatherVM x:Name="viewModelInDataContext"/>
</Page.DataContext>
<Grid>
<AutoSuggestBox Margin="40" QueryIcon="Find"
PlaceholderText="Search"
Text="{Binding Path=city, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
In code-behind, suppose you need to assign a value to the city.
this.viewModelInDataContext.city = "Orlando";
Update:
The ViewModel you showed has some issues. First, you need to assign a value to the cityData property, if not, the text of AutoSuggestBox which bound with the cityData will always display empty. Second, you need to implement INotifyChanged in WeatherVM. When your cityData changes, the text of the AutoSuggestBox will change. Third, do not set the property type to asynchronous like Task, this will affect the display, it's better to set string cityData.
public class WeatherVM : INotifyPropertyChanged
{
public WeatherVM(){
GetData();
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _cityData;
public string cityData
{
get
{
return _cityData;
}
set
{
_cityData = value;
OnPropertyChanged();
}
}
private async void GetData()
{
cityData = await MapLocator.GetCityData();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a combobox displaying the lists within it perfectly in the ViewModel but I'm looking to have it so that when a selected item from the list is chosen it fires the ViewModel screen and I only want one from the list to do this?
So here is what I have in the ChooseView:
<ComboBox x:Name="CatalogName1" SelectedItem="{Binding SelectedCatalog1}" Style="{DynamicResource appComboBox}" Grid.Row="1" Grid.Column="1" >
</ComboBox>
and in the ChooseViewModel:
public List<string> CatalogName1
{
get
{
return new List<string> { "New", "Replace", "Extended", "Nothing", "ShowScreen" };
}
}
private string selectedCatalog1;
public string SelectedCatalog1
{
get
{
return this.selectedCatalog1;
}
set
{
this.selectedCatalog1 = value;
this.NotifyOfPropertyChange(() => this.SelectedCatalog1);
}
}
the "ShowScreen" in the combo list should display the ShowScreenViewModel but I have tried with the getter setter and it's not making sense to me
Okay, this is the way I would fix the problem...
private string selectedCatalog1;
public string SelectedCatalog1
{
get
{
return selectedCatalog1;
}
set
{
selectedCatalog1 = value;
ValidateValue(value);
NotifyOfPropertyChange(() => SelectedCatalog1);
}
}
private void ValidateValue(string s)
{
if (s == "ShowScreen")
{
ActivateItem(new ShowScreenViewModel());
}
}
I have two viewmodel, on the first viewmodel i have a listbox:
<ListBox x:Name="MainMenu" toolkits:TiltEffect.IsTiltEnabled="True"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemTemplate="{StaticResource MainMenu}"
ItemsSource="{Binding Categories}" Margin="0,97,0,0"
Tap="MainMenu_Tap">
In the second page, i have a listpicker
<toolkit:ListPicker Margin="0,153,0,0" Background="{StaticResource PhoneAccentBrush}" VerticalAlignment="Top"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding Item}"
ItemTemplate="{StaticResource CategorySelector}"
FullModeHeader="Category"
FullModeItemTemplate="{StaticResource FullCategorySelector}"
BorderBrush="{StaticResource PhoneAccentBrush}"/>
What i want is when I navigate to second page, the selected item in the first page will be selected in the second page. But I always get the selected item must always set to a valid value when I navigate to second page.
first viewmodel
private CategoryModel _selectedItem = null;
public CategoryModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value)
{
return;
}
var oldValue = _selectedItem;
_selectedItem = value;
RaisePropertyChanged("SelectedItem", oldValue, value, true);
}
}
second viewmodel
private CategoryModel _item = null;
public CategoryModel Item
{
get { return _item; }
set
{
if (_item == value)
{
return;
}
var oldValue = _item;
_item = value;
// Update bindings, no broadcast
RaisePropertyChanged("Item");
}
}
EDIT
When I change the listpicker in the second page to Listbox, it works pretty well.
So this is an issue enter link description here. How should I do to get this thing work with the listpicker?
I think you're confusing views and viewmodels.
Because you're binding the selected item in XAML, when the XAML is parsed and the page created it's trying to bind to an item in a collection which hasn't been created yet. This is why the comments on the bug suggest a work around when setting this in code behind.
In your Tap handler on the first page, I assume that you're passing some details of the selected item to the second page. You could, therefore, remove the XAML binding of the selected item and in the OnNavigatedTo event handler on the second page set the binding in code, once you know the ItemsSource has been populated.
Alternatively, you could consider having the two pages share the same viewmodel instance.
ListPicker uses Items.IndexOf to get the index of item instance that should select.
If the instance does not match (it is not an object instance from the collection) the IndexOf will return -1 and the InvalidOperationException is thrown with the message: "SelectedItem must always be set to a valid value".
Override Equals method of the type in the collection and it will work as expected.
Example:
public override bool Equals(object obj)
{
var target = obj as ThisTarget;
if (target == null)
return false;
if (this.ID == target.ID)
return true;
return false;
}
Hope it helps
I was just reading Rx HOL NET. Upon finding (example uses Windows Forms):
var moves = Observable.FromEvent<MouseEventArgs>(frm, "MouseMove");
I wonder how can I instantiate and pass the reference to moves to ViewModel in some WPF MVVM setup? In my understanding it does make sense to try and filter this stream of data inside ViewModel.
Or, how to do something similar for keyboard input into TextBox? In this scenario you wouldn't, for example, attach some text masking behavior to a control in XAML but would, instead, let Observer in VM filter and validate keyboard input.
Am I completely off the track?
Here is an example of how you could implement the web service dictionary in a MVVM fashion. It has three parts:
The ObservablePropertyBacking class, a backing for properties (of T) that also implements IObservable
The MyViewModel class. It contains a property CurrentText which uses an ObservablePropertyBacking as backing storage. It also observes the value of this property and uses it to call the dictionary web service.
The MainView.xaml which contains a TextBox. Its Text property is two-way bound to the CurrentText property on the view model.
MyViewModel.cs:
class MyViewModel: INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
#endregion
public MyViewModel()
{
SetupProperties();
}
#region CurrentText
/* We use a special class for backing of the CurrentText property. This object
* holds the value of the property and also dispatches each change in an observable
* sequence, i.e. it implements IObservable<T>.
*/
private ObservablePropertyBacking<string> _textInput;
public string CurrentText
{
get { return _textInput.Value; }
set
{
if (value == _textInput.Value) { return; }
_textInput.Value = value;
RaisePropertyChanged("CurrentText");
}
}
#endregion
/* Create property backing storage and subscribe UpdateDictionary to the observable
* sequence. Since UpdateDictionary calls a web service, we throttle the sequence.
*/
private void SetupProperties()
{
_textInput = new ObservablePropertyBacking<string>();
_textInput.Throttle(TimeSpan.FromSeconds(1)).Subscribe(UpdateDictionary);
}
private void UpdateDictionary(string text)
{
Debug.WriteLine(text);
}
}
ObservablePropertyBacking.cs:
public class ObservablePropertyBacking<T> : IObservable<T>
{
private Subject<T> _innerObservable = new Subject<T>();
private T _value;
public T Value
{
get { return _value; }
set
{
_value = value;
_innerObservable.OnNext(value);
}
}
public IDisposable Subscribe(IObserver<T> observer)
{
return _innerObservable
.DistinctUntilChanged()
.AsObservable()
.Subscribe(observer);
}
}
MainPage.xaml:
<Window
x:Class="RxMvvm_3435956.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox
Text="{Binding CurrentText, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
This might help: Reactive Extensions (Rx) + MVVM = ?
The easiest way of doing the keyboard sample would be to two-way bind the text to a property of the ViewModel. The Text setter could then write to a private Subject that the rest of your code uses as a basis of IObservable<string>. From there, you can complete the HOL sample.
Mouse movements are generally considered too "view" to put in the ViewModel, but if the logic that came off it was complex enough, you could have it execute an ICommand or perhaps put the logic into a behavior. If it were an ICommand, you could have the command have a WhenExecuted IObservable property that you could pick up in your ViewModel.`