MVVM Combobox binding - mvvm

I have a combobox that doesn't seem to be updated it's view model.
On the view I have
<ComboBox Grid.Row="0"
Grid.Column="1"
ToolTip="Current rank of the officer"
ItemsSource="{Binding Path=RanksAvailable}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=SelectedRank, Mode=TwoWay}"/>
in the view model I have
public List<Rank> RanksAvailable {get; set;}
private Rank _selectedRank;
public Rank SelectedRank
{
get { return _selectedRank; }
set
{
if (_selectedRank != value)
{
_selectedRank = value;
this.isDirty = true;
RaisePropertyChanged("SelectedRank");
}
}
}
the combobox is being populated alright, I just can't seem to get a value out of it.

The problem is you are using SelectedValuePath="Name" just remove it and it will work.
Your ComboBox will become-
<ComboBox Grid.Row="0"
Grid.Column="1"
ToolTip="Current rank of the officer"
ItemsSource="{Binding Path=RanksAvailable}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=SelectedRank, Mode=TwoWay}"/>

Related

Child property of an ObservableProperty is not updating

Something isn't right with the XAML but it's not sticking out at me.
I've been working on the layout of one of my .net Maui XAML pages. I added a collectionView when I noticed that the top data was no longer showing. The other pages are working fine.
What's weird is that the data is there and while running the app in debug mode if I highlight, shift-delete, then paste it back in the bound data appears. I also noticed if I change the {Binding EditEvent.name} by removing the "name" from EditEvent then adding it back on, the view displays the data as well.
But if I leave and navigate back in the data won't show up until I repeat the above process. It's like the viewModel isn't updating the view when the data changes. But if I force the view to update by deleting and re-pasting it will show it.
Anyone have an idea what possibly could be the issue?
I've got 2 ObservableProperties in my ViewModel:
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Newtonsoft.Json;
using SharedModels;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyApp.ViewModels
{
public partial class EditEventViewModel : ObservableObject
{
#region XAML page Observables
[ObservableProperty]
attEventDx editEvent;
[ObservableProperty]
ObservableCollection<groupReturn> groupsItems;
#endregion
// pass object to edit into this view
public async void SetEditEvent(attEventDx incomingEvent)
{
editEvent = incomingEvent;
//await LoadGroupsAsync();
}
...
}
And this is the view:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Pages.EditEventPage"
Title="Edit Event"
xmlns:viewmodel="clr-namespace:MyApp.ViewModels"
xmlns:dm="clr-namespace:SharedModels;assembly=SharedModels"
x:DataType="viewmodel:EditEventViewModel"
NavigatedTo="ContentPage_NavigatedTo">
<VerticalStackLayout>
<Grid HorizontalOptions="Center" VerticalOptions="Start" Padding="0,40,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Label Text="Event Name" VerticalOptions="Center" HorizontalOptions="Center" Grid.Column="0" Grid.Row="0"/>
<Entry Text="{Binding EditEvent.name}" WidthRequest="200" Grid.Column="1" Grid.Row="0"/>
<Label Text="Event Date" VerticalOptions="Center" HorizontalOptions="Center" Grid.Column="0" Grid.Row="1"/>
<Entry Text="{Binding EditEvent.happeningOn}" WidthRequest="200" Grid.Column="1" Grid.Row="1"/>
</Grid>
<Label Text="Selectable Groupings" VerticalOptions="Center" HorizontalOptions="Center" Padding="20"/>
<CollectionView ItemsSource="{Binding GroupsItems}" SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="dm:groupReturn">
<SwipeView>
<SwipeView.RightItems>
<SwipeItem Text="Delete" BackgroundColor="Red"/>
</SwipeView.RightItems>
<Grid Padding="0,5">
<Label Text="Groups"/>
<ScrollView>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:EditEventViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Label Text="{Binding groupName}" FontSize="20" FontAttributes="Bold"/>
</Frame>
</ScrollView>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
this is my xaml.cs for that page:
public partial class EditEventPage : ContentPage, IQueryAttributable
{
EditEventViewModel _vm;
attEventDx _editEvent;
public EditEventPage( EditEventViewModel vm)
{
InitializeComponent();
_vm = vm;
BindingContext = _vm;
}
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
_editEvent = query["EditEvent"] as attEventDx;
}
private void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
_vm.SetEditEvent(_editEvent);
}
}
attEventDx for reference (sits in another shared project between Azure Functions and the mobile app):
namespace SharedModels
{
public class attEventDx
{
public Guid? publicId { get; set; }
public int? createdBy { get; set; }
public string name { get; set; }
public DateTime dateCreated { get; set; }
public DateTime? happeningOn { get; set; }
}
}
As I referred to this is the page that IS working:
xaml.cs:
public partial class EventPage : ContentPage
{
EventViewModel _vm;
public EventPage(EventViewModel vm)
{
InitializeComponent();
_vm = vm;
BindingContext= _vm;
}
private async void ContentPage_NavigatedTo(object sender, NavigatedToEventArgs e)
{
await _vm.LoadEventData();
}
private void ImageButton_Clicked(object sender, EventArgs e)
{
}
}
ViewModel:
public partial class EventViewModel : ObservableObject
{
#region XAML page Observables
[ObservableProperty]
ObservableCollection<attEventDx> eventItems;
[ObservableProperty]
attEventDx selectedEvent;
[ObservableProperty]
string text;
#endregion
public EventViewModel()
{
//EventItems = new ObservableCollection<attEventDx>();
}
[RelayCommand]
public async Task LoadEventData()
{
MyApp.globals.SetHttpClient();
try
{
var response = await MyApp.globals.httpClient.GetAsync(MyApp.globals.APIURL + "getEvents");
var allEvents = response.Content.ReadAsStringAsync().Result;
if (allEvents != null)
{
List<attEventDx> listOfEvents = JsonConvert.DeserializeObject<List<attEventDx>>(allEvents);
if (listOfEvents != null)
{
EventItems = new ObservableCollection<attEventDx>(listOfEvents);
}
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message + "\r\b" + ex.StackTrace);
}
}
[RelayCommand]
async Task Add()
{
await Shell.Current.GoToAsync($"{nameof(AddEventPage)}");
}
[RelayCommand]
async Task Tap(attEventDx sender)
{
selectedEvent = sender;
var navigationParameter = new Dictionary<string, object>
{
["EditEvent"] = selectedEvent
};
await Shell.Current.GoToAsync($"{nameof(EditEventPage)}", navigationParameter);
}
[RelayCommand]
async Task Refresh()
{
await LoadEventData();
}
}
And the view of the working page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.EventPage"
Title="Events"
xmlns:viewmodel="clr-namespace:MyApp.ViewModels"
xmlns:dm="clr-namespace:SharedModels;assembly=SharedModels"
x:DataType="viewmodel:EventViewModel"
NavigatedTo="ContentPage_NavigatedTo">
<Grid RowDefinitions="100, Auto, 30, *"
ColumnDefinitions=".50*, .25*, .25*"
Padding="10">
<Image Grid.ColumnSpan="3"
Source="logo.png"
BackgroundColor="Transparent"/>
<ImageButton Source="plus.png" Grid.Row="0" Grid.Column="2" Scale=".7" Command="{Binding AddCommand}"></ImageButton>
<Label Text="New Event" Grid.Column="2" Grid.Row="0" HorizontalOptions="Center" VerticalOptions="End"></Label>
<!--<Entry Placeholder="Enter Text" Grid.Row="1" Text="{Binding Text}" />-->
<!--<Button Text="Search" Grid.Row="1" Grid.Column="1" />-->
<!--<Button Text="Add" Grid.Row="1" Grid.Column="2" Command="{Binding AddCommand}"/>-->
<Label Text="Upcoming Events" FontSize="22" Grid.Row="2"/>
<!--<Button Text="Refresh" Grid.Row="2" Grid.Column="2" Command="{Binding RefreshCommand}"/>-->
<CollectionView Grid.Row="3" Grid.ColumnSpan="3" ItemsSource="{Binding EventItems}" SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="dm:attEventDx">
<SwipeView>
<SwipeView.RightItems>
<SwipeItem Text="Delete" BackgroundColor="Red"/>
</SwipeView.RightItems>
<Grid Padding="0,5">
<Label Text="Event"/>
<ScrollView>
<Frame>
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:EventViewModel}}, Path=TapCommand}"
CommandParameter="{Binding .}" />
</Frame.GestureRecognizers>
<Label Text="{Binding name}" FontSize="20" FontAttributes="Bold"/>
</Frame>
</ScrollView>
<Label Text="{Binding happeningOn}" HorizontalOptions="End" VerticalOptions="Center" Padding="0,0,5,0"></Label>
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
Well, what ToolmakerSteve told me kind of worked, the items were initially displaying but not updating.
I then decided to build out another page in the app and do some experimenting along the way and figured out the issue.
When I created the new page by hand, I still had this issue and was doubting my sanity. I then partially copied in the page that was working and it worked! In comparing the two pages closely, I finally discovered what the problem was.
I was right, the CommunityToolkit's [ObservableProprerty] DOES work for all child items in an object; this is why I selected using this library from the start. I wasn't going crazy... (At least not on this)
This particular app was started a few months ago but then I got pulled into another project in another platform for a few months so what I had learned was partially forgotten when I picked it back up recently.
When you define a [ObservableProperty] like this:
[ObservableProperty]
myObject usedVariable;
The "usedVariable" will contain the data, but not the framework for INotifyPropertyChanged. CommunityToolkit builds out the framework on "UsedVariable".
While this code is "legal" in the ViewModel:
usedVariable = new myObject();
It will assign the data, but not the notification framework.
Instead it needs to be:
UsedVariable = new myObject();
Once the variable is defined with lowercase, you will never reference the variable that way again (as far as I can tell anyway). Instead, you will use the uppercase "UsedVariable".
When I referenced the lowercase version of the variable, I didn't see the data on the app page when it started. However, if I had the page open and I removed the XAML code for that control and pasted it back in, the data did show.
It's always something simple that causes the most grief...

Content view binding not working

I have ContentView which need ViewModel binding
Test.xaml
<ContentView.Content>
<Frame x:Name="HelpBaseFrame" BackgroundColor="White" CornerRadius="16" HorizontalOptions="Fill" VerticalOptions="Center">
<StackLayout>
<ListView HasUnevenRows="True" x:Name="lstview" SeparatorColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell IsEnabled="False">
<ScrollView x:Name="ScrollView" Orientation="Vertical" Padding="0, 1, 0, 0" BackgroundColor="Transparent" HorizontalOptions="Center">
<StackLayout>
<Label x:Name="LabelHeader" FontAttributes="Bold" Font="HiraginoSans-W6, 16"
HorizontalOptions="Center" Margin="0,20,0,0">
<Label.Text>
<Binding Path="HeaderData"></Binding>
</Label.Text>
</Label>
<local:LineSpacingLabel x:Name="LabelHeaderDesceiption" LineSpacing="6"
Font="HiraginoSans-W3, 16" FontAttributes="None" Margin="0,20,0,0">
<Label.Text>
<Binding Path="DescriptionData"></Binding>
</Label.Text>
</local:LineSpacingLabel>
</StackLayout>
</ScrollView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</Frame>
</ContentView.Content>
BindingClass
public void SetData(Dictionary<string, string> dictionary)
{
............
lstview.ItemsSource = HelpDataList; // HelpDataList is observable collection of HElp Data
}
Model class :
public class HelpData : BaseViewModel
{
private string Header = string.Empty;
private string Description = string.Empty;
public string HeaderData
{
get { return Header; }
set
{
Header = value;
OnPropertyChanged("HeaderData");
}
}
public string DescriptionData { get; set; }
}
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?
This view model for above view.
This binding is not working.
Is anything wrong?
You have to set bindingContext on the target control by using x:Reference markup extension.
BindingContext="{x:Reference Name=ViewModelField}"
Or
BindingContext="{x:Reference ViewModelField}"
Label.Text should be binding with string not viewModel.
Text="{Binding Path=Value}"
Or
Text="{Binding Value}" ( “Path=” part of the markup extension can be omitted if the path is the first item in the Binding markup extension)
Refer to Bindings
Update
<Binding> is not a valid tag.
Modify the label :
<Label Text="{Binding Path = HeaderData}">

Flipview not update SelectedItem

I have FlipView like this
<FlipView Grid.Row="1" Grid.RowSpan="2" HorizontalContentAlignment="Center" x:Name="BookPageContentFlipView" ItemsSource="{Binding BookPagesNew,Mode=OneWay}"
SelectedItem="{Binding SelectedPage,Mode=TwoWay}"
SelectionChanged="BookPageContentFlipViewSelectionChanged" >
<FlipView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Background="Transparent" Orientation="Horizontal"
VirtualizationMode="Recycling" AreScrollSnapPointsRegular="True" />
</ItemsPanelTemplate>
</FlipView.ItemsPanel>
<FlipView.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Center" Width="650" x:Name="GridWebView">
<WebView
common:HTMLStringExtension.HTML="{Binding HTMLString}"
ScriptNotify="OnBookPageContentWebViewScriptNotify"
Tapped="OnBookPageContentFlipViewTapped" />
<Image Source="ms-appx:///Assets/add-bookmark.png" x:Name="BookmarkImage"
Tapped="OnBookmarkImageTapped" HorizontalAlignment="Right" VerticalAlignment="Top"
Width="38"
Height="38" />
</Grid>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
I am using MVVM and update this flipview ItemsSource from VM. Now my problem is when I am update ItemsSource from VM and use NotifyPropertyChanged() to notify View to update the flipview ItemsSource, my selected flipview not updating the view with new data.
But after I move about > 2 item (next/previous) item the view correctly updated. How I can force my flipview to update the currently selected item without need to reload flipview?
I think that BookPagesNew needs to implement the interface INotifyPropertyChanged
because if you change only one property of an item in a list the view doesn't get the notification.
you can use this code to implement the interface
public class BookPagesNew : INotifyPropertyChanged
{
public string HTMLString { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
in your VM BookPagesNew should be an ObservableCollection<BookPagesNew>

How to apply view-side filtering in MVVM?

I'm using Telerik RadGridView in my project (which essentially is a standart GridView).
This component has its own filtering functionality and I want to get advantage of it.
Filtering itself I am planning to do based on several combobox selected values. So if I got right idea of MVVM, I need to bind the combos to some ViewModel's properties. But here's a problem of how to pass these selected values back to View's component? how to make it refresh filtering as selected value change?
upd: I use SimpleMVVM framework.
XAML of MainWindow:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
......
DataContext="{Binding Source={StaticResource Locator}, Path=MainPageViewModel}">
<StackPanel Height="auto">
<telerik:RadMenu VerticalAlignment="Top">
......
</telerik:RadMenu>
<my:Expander VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="auto" Width="auto"/>
<my:CustomerView Margin="0,0,0,0" VerticalAlignment="Top" HorizontalAlignment="Stretch" Height="auto" Width="auto"/>
</StackPanel>
XAML of expander:
<UserControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...... >
<Grid Margin="10,10,0,10" Width="684" Height="97" VerticalAlignment="Top" HorizontalAlignment="Left" >
<telerik:RadExpander x:Name="radExpander" IsExpanded="True" HorizontalAlignment="Stretch" VerticalAlignment="Top" telerik:AnimationManager.IsAnimationEnabled="True" Margin="0,0,0,0" Grid.RowSpan="2">
<telerik:RadComboBox HorizontalAlignment="Left" Margin="244,-2,0,0" VerticalAlignment="Top" Width="154" Height="26"
ItemsSource="{Binding Path=AllLevels}" DisplayMemberPath="name" SelectedItem="{Binding SelectedEventLevel}"/>
.......
</Grid>
</telerik:RadExpander>
</Grid>
XAML of CustomerView:
<telerik1:RadGridView Name="EventList" .... ItemsSource="{Binding SportEventsList}" AutoGenerateColumns="False">
<telerik1:RadGridView.Columns>
.....
</telerik1:RadGridView.Columns>
</telerik1:RadGridView>
Snippet of Viewmodel's code:
private ObservableCollection<sportevent> _sportEventsList;
public ObservableCollection<sportevent> SportEventsList
{
get { return _sportEventsList; }
set
{
_sportEventsList = value;
NotifyPropertyChanged(vm => vm.SportEventsList);
}
}
Add the following properties to your VM:
private ObservableCollection<yourType> allLevels;
public ObservableCollection<yourType> AllLevels
{
get
{
return allLevels;
}
set
{
allLevels = value;
RaisePropertyChanged("AllLevels");
}
}
private yourType selectedEventLevel;
public yourTypeSelectedEventLevel
{
get
{
return selectedEventLevel;
}
set
{
selectedEventLevel = value;
RaisePropertyChanged("SelectedEventLevel");
}
}
I assume MainPageViewModel inherits SimpleViewModelBase

How to pass two parameters to ViewModel class in Silverlight?

I am studying to use MVVM pattern for my Silverlight application.
Following code is from xaml UI code :
<Button Width="30"
Margin="10"
Content="Find"
Command="{Binding Path=GetCustomersCommand, Source={StaticResource customerVM}}"
CommandParameter="{Binding Path=Text, ElementName=tbName}"/>
<TextBox x:Name="tbName"
Width="50" />
<TextBox x:Name="tbID"
Width="50" />
And following code is from ViewModel class :
public ICommand GetCustomersCommand
{
get { return new RelayCommand(GetCustomers) { IsEnabled = true }; }
}
public void GetCustomers(string name, string id)
{
// call server service (WCF service)
}
I need to pass two parameters, however, can't find out how to pass two parameters(id and name) to ViewModel class.
I'd like to know if it is possible in xaml code not in the codebehind.
Thanks in advance
There's no easy way to do it. Instead, I suggest you make a command with no parameters, and bind box TextBoxes to properties of your ViewModel:
C#
public void GetCustomers()
{
GetCustomers(_id, _name);
}
private int _id;
public int ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
XAML
<Button Width="30"
Margin="10"
Content="Find"
Command="{Binding Path=GetCustomersCommand, Source={StaticResource customerVM}}"/>
<TextBox x:Name="tbName"
Text="{Binding Path=Name, Source={StaticResource customerVM}, Mode=TwoWay}"
Width="50" />
<TextBox x:Name="tbID"
Text="{Binding Path=ID, Source={StaticResource customerVM}, Mode=TwoWay}"
Width="50" />