Trouble binding to a textblock - Windows Phone - MVVM - c#

I have spent a couple hours trying to figure out this ONE problem. Here's what is happening:
I am trying to bind a Title to my XAML file from my ViewModel. All of the code executes (I checked using breakpoints/watch), but the binding doesn't actually work. I am very new to development and especially MVVM, so I am having a hard time figuring this out. Relevant code:
App.Xaml.Cs
private static MainPageViewModel _mainPageViewModel = null;
public static MainPageViewModel MainPageViewModel
{
get
{
if (_mainPageViewModel == null)
{
_mainPageViewModel = new MainPageViewModel();
}
return _mainPageViewModel;
}
}
MainPageModel
public class MainPageModel : BaseModel
{
private string _pageTitle;
public string PageTitle
{
get { return _pageTitle; }
set
{
if (_pageTitle != value)
{
NotifyPropertyChanging();
_pageTitle = value;
NotifyPropertyChanged();
}
}
}
MainPageViewModel
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
MainPageViewModel
public MainPageViewModel()
{
LoadAll();
}
MainPage.Xaml.Cs
public MainPage()
{
InitializeComponent();
DataContext = App.MainPageViewModel;
}
MainPage.Xaml
<Grid x:Name="LayoutRoot">
<phone:Panorama Title="{Binding PageTitle}">
Do I need a using statement in the Xaml too? I thought I just needed to set the data context in the MainPage.Xaml.Cs file.
I'm pretty sure I've posted all of the relevant code for this. Thanks everyone!!

The problem is here, in the view model class:
private void LoadAll()
{
var page = new MainPageModel();
page.PageTitle = "title";
All you've done here is create a local object "page" -- this will not be accessible anywhere outside the local scope. I suppose what you meant to do is make "page" a member of "MainPageViewModel":
public class MainPageViewModel
{
public MainPageModel Model { get; private set; }
private void LoadAll()
{
_page = new MainPageModel();
_page.PageTitle = "title";
}
}
This way, you'll be able to bind to the "PageTitle" property -- but remember, it's a nested property, so you'll need:
<phone:Panorama Title="{Binding Model.PageTitle}">

Related

How to delete data in a .NET MAUI list

I am currently doing a project with the MVVM method in NET MAUI to add, modify and delete drivers.
I have a template that contains the name, first name and number of points of the driver.
Then I have two views each with a model view:
- One that represents the list of my drivers with the possibility to add a driver, to select a driver from the list by going to another page (PageListPilotViewModel).
- And another one which represents the selected driver in another page to be able to modify its data and the possibility of removing it. (ProfilePilotViewModel)
At the moment I can select, add the driver and modify the driver in the other page. But I can't delete the driver in the profile page.
Here is what I have done so far:
-> Models : Pilote Model
public class PiloteModel : INotifyPropertyChanged
{
private string _nom;
public string Nom
{
get { return _nom; }
set { _nom = value; OnPropertyChanged(); }
}
private string _prenom;
public string Prenom
{
get { return _prenom; }
set { _prenom = value; OnPropertyChanged(); }
}
private int _points;
public int Points
{
get { return _points; }
set { _points = value; OnPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
-> View : ProfilPilotePage
<vm:PageListPiloteViewModel></vm:PageListPiloteViewModel>
</ContentPage.BindingContext>
<VerticalStackLayout>
<StackLayout>
<Entry Text="{Binding Pilote.Nom, Mode=TwoWay}" Placeholder="{Binding Nom}"></Entry>
<Entry Text="{Binding Pilote.Prenom}" Placeholder="{Binding Pilote.Prenom}"></Entry>
<Entry Text="{Binding Pilote.Points}" Placeholder="{Binding Pilote.Points}"></Entry>
<Button Command="{Binding OnsupprimerPiloteCommand}">
</Button>
</StackLayout>
-> code behind the profilePilotPage view
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
private void OnSupprimerPiloteClicked(object sender, PiloteModel e)
{
_viewModel.ListePilotes.Remove(e);
}
-> model views : PageListPilotViewModel , to be able to delete also the driver in the list
public ICommand OnsupprimerPiloteCommand { get; set; }
public PageListPiloteViewModel()
{
ValiderCommand = new Command(AjouterPilote);
OnsupprimerPiloteCommand = new Command(OnSupprimerPiloteClicked);
SelectedPilote = new PiloteModel();
ListePilotes = new ObservableCollection<Models.PiloteModel>();
ListePilotes.Add(new Models.PiloteModel { Nom = "Fabio", Prenom = "Quartaro", Points = 215 });
}
private void OnSupprimerPiloteClicked()
{
SupprimerPiloteClicked?.Invoke(this, SelectedPilote);
}
->code behind the PageListPiloteView: with the error I encounter on the last :
await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) :
CS7036 Error None of the specified arguments match the 'viewModel' mandatory parameter of 'ProfilePilotPage.ProfilePilotPage(PageListPilotViewModel)'
private async void SelectionnerPilote(object sender, SelectionChangedEventArgs e)
{
PiloteModel selectedPilote = (PiloteModel)((CollectionView)sender).SelectedItem;
ProfilPiloteViewModel viewModel = new ProfilPiloteViewModel();
viewModel.Pilote = selectedPilote;
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
}
}
Do you have any idea how to make the specified arguments mandatory please ?
You've mixed up constructor and initializer.
This line
await Navigation.PushAsync(new ProfilPilotePage{ BindingContext = viewModel });
should be
await Navigation.PushAsync(new ProfilPilotePage(viewModel));
The reason for this is that you're defining an argument in the signature of the ProfilPilotePage's constructor:
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
//...
}
Therefore, you must pass the ViewModel argument.
At first, you can try to use the ewerspej's solution or only add a default construction method without any parameter into the ProfilPilotePage to fix the error caused by await Navigation.PushAsync(new ProfilePilotPage{ BindingContext = viewModel }) . Such as:
public partial class ProfilPilotePage : ContentPage
{
private PageListPiloteViewModel _viewModel;
public ProfilPilotePage()
{
InitializeComponent();
}
public ProfilPilotePage(PageListPiloteViewModel viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.SupprimerPiloteClicked += OnSupprimerPiloteClicked;
BindingContext = _viewModel;
}
}
And then I saw you used both the mvvm and the code behind. You can remove the OnSupprimerPiloteClicked(object sender, PiloteModel e) in the page.cs and change the OnSupprimerPiloteClicked() in the view model. Such as:
private void OnSupprimerPiloteClicked()
{
ListePilotes.Remove(SelectedPilote);
}
Finally, I saw SelectedPilote = new PiloteModel(); in your viewmodel. Which item in the list did you want to delete? I think it should be the seleted item not a new PiloteModel().

MVVMCross How to display a view within a view

I'm a new to the MVVMCross package, and C# for that matter. I've spent the better part of the day trying to figure out what I'm not understanding reading the documentation on presenters and navigation, etc. in order to try to understand, but I'm missing something.
I originally created a WPF app not implementing MVVM and now I wanted to convert, but I'm struggling with this part. I want to have a Main Menu that is part of a grid in a "MainWindow" like shell where the remaining portion of the page (and grid column 2) are used to display a nested view.
Ultimately, I’m just trying to reproduce the same layered controls in the original WPF application. In that app there is a content control Which takes up most of the form whose content property is set to a different form depending on the users selection.
MainWindow.xaml.cs
public partial class MainWindow : MvxWindow
{
public MainWindow(IMvxNavigationService navService)
{
InitializeComponent();
DataContext = new MainViewModel(navService);
//content.Content = new AdminMenuView();
}
}
MainViewModel.cs
private MvxViewModel _nextMenuContent;
public MainViewModel(IMvxNavigationService navService)
{
_navService = navService;
MoveMenuCommand = new MvxCommand(MoveMenu);
ChildViewModel = new AdminMenuViewModel();
GoToAdminMenu = new MvxCommand(SelectAdminMenu);
}
MainView.xaml
<ContentControl Content="{Binding ChildViewModel}"/>
***The grid and columns are all working fine
MainView.xaml.cs
public partial class MainView : MvxWpfView
{
public MainView()
{
InitializeComponent();
}
}
AdminMenuModel.cs
public class AdminMenuViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navService;
public AdminMenuViewModel()
{
Initialize();
}
public override void Prepare()
{
base.Prepare();
}
public override async Task Initialize()
{
await base.Initialize();
}
}
AdminMenuModel.xaml.cs
public partial class AdminMenuView : MvxWpfView
{
public AdminMenuView()
{
InitializeComponent();
}
public new AdminMenuViewModel ViewModel
{
get { return base.ViewModel as AdminMenuViewModel; }
set { base.ViewModel = value; }
}
}
When I call the AdminMenuViewModel it runs, but all I get in the content control is either a blank screen if I Bind the "ChildViewModel" to the DataContext property of the content control and a string of the path to the AdminMenuViewModel if I bind it to the content property.
You have to set MainViewModel as DataContext of your main window
public MainWindow(IMvxNavigationService navService)
{
DataContext = new MainViewModel(navService);
InitializeComponent();
}

UWP MVVM binding to TextBox and passing back value

I am trying to get the content of a TextBox updated using Binding in a MVVM environment. When a Button receive focus, it passes a value, and that value should be reflected in the TextBox. I seem to have the first part right, however seems to be struggling at passing the value..
I know the question about MVVM has been asked before (including by myself), but I really cannot get it, for some reasons..
So I start with my model:
public class iText : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _text;
public string Text
{
get { return _text; }
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}
I then continue with my ViewModel:
private iText _helper = new iText();
public iText Helper
{
get { return _helper; }
set
{
_helper = value;
}
}
The XAML page:
<Page.Resources>
<scan:ModelDataContext x:Key="ModelDataContext" x:Name="ModelDataContext"/>
</Page.Resources>
<TextBox Text="{Binding Helper.Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I then try to update the Text from MainPage.cs
public sealed partial class MainPage : Page
{
public MainPageViewModel iText { get; set; }
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
}
private void btn_GotFocus(object sender, RoutedEventArgs e)
{
var str = "test"
iText.Helper.Text = str;
}
I could really appreciate if someone could tell me what I do wrong, and where. Thanks so much in advance.
In your MainPage constructor, try setting the datacontext to your ViewModel.
Something like...
public MainPage()
{
InitializeComponent();
iText = new MainPageViewModel();
this.dataContext = iText;
}

Using new ViewModel each time I open a page

I didn't know how better to word the title so I went with solution that came to my mind.
Here is the problem. I have a page that has list and each item on the lists opens a detail page (on click). But the VM is reused, which causes me several problems.
Previous data can be seen for split second when opening a the detail page
I need certain properties to be set to specific values when the page open, but since the VM is reused it keeps all the values from the previous detail and this messes up my logic.
This UWP app. I'm using Template10 framework's NavigationService to move between pages.
Main Page ViewModel
public class MainPageViewModel : ViewModelBase {
private List<MangaItem> _mangaList;
public List<MangaItem> mangaList {
get { return _mangaList; }
set { Set(ref _mangaList, value); }
}
private string _mainSearchText;
public string mainSearchText {
get { return _mainSearchText; }
set { Set(ref _mainSearchText, value); }
}
public MainPageViewModel() {
_mangaList = new List<MangaItem>();
mangaList = new List<MangaItem>();
Initialize();
}
private async void Initialize() {
mangaList = await MangaListGet.GetListAsync();
}
public async void MainSearchSubmitted() {
mangaList = await MangaListGet.GetListAsync(_mainSearchText);
}
public void MangaSelected(object sender, ItemClickEventArgs e) {
var mangaItem = (MangaItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.MangaDetail), mangaItem.id);
}
}
And Detail Page ViewModel
class MangaDetailViewModel : ViewModelBase {
private MangaItem _mangaDetail;
public MangaItem mangaDetail {
get { return _mangaDetail; }
set { Set(ref _mangaDetail, value); }
}
private string _mangaId;
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState) {
_mangaId = parameter as string;
Initialize();
await Task.CompletedTask;
}
private async void Initialize() {
mangaDetail = await MangaDetailGet.GetAsync(_mangaId);
}
public void ChapterSelected(object sender, ItemClickEventArgs e) {
var _chapterId = (ChapterListItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.ChapterPage), _chapterId.id);
}
}
This code only shows the first problem is displaying previously loaded data for a split second. If needed I will add code that showcases the other problem, but I' not sure if it's really relevant right now. I'm thinking that maybe my entire logic is flawed or something.
EDIT:
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
where vm is xmlns:vm="using:MangaReader.ViewModels".
Another solution is to use Bootstrapper.ResolveforPage() which is intended to handle dependency injection but would easily serve your needs. Like this:
[Bindable]
sealed partial class App : BootStrapper
{
static ViewModels.DetailPageViewModel _reusedDetailPageViewModel;
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
if (page.GetType() == typeof(Views.DetailPage))
{
if (_reusedDetailPageViewModel == null)
{
_reusedDetailPageViewModel = new ViewModels.DetailPageViewModel();
}
return _reusedDetailPageViewModel;
}
else
{
return null;
}
}
}
The NavigationService will treat this the same as any other view-model. Meaning it will call OnNavTo() and the other navigation overrides you include.
Best of luck.
While Template10 documentation states the NavigationCacheMode is disabled by default, that isn't the case in it's example templates (as of writing this). This is set in View C# code (.xaml.cs file).
.xaml.cs file
namespace MangaReader.Views {
public sealed partial class MangaDetail : Page {
public MangaDetail() {
InitializeComponent();
//NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; //this was set by default
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled;
}
}
}
Now, new ViewModel will be created each time you access a this page.

What is wrong with that binding?

I am working on a Windows 8 App using C# and Xaml as well as the MVVM-Light Toolkit.
I set everything up to create a proper binding to an ObservableCollection that gets its Data from a local database but it does not work well. It works when I edit the get property of the ObservableCollection to something like:
get
{
_Subjects.Add(new SubjectViewModel { Name = "Test" });
return _Subjects;
}
That displays the "Test"-Subject but still not the Subjects from the Database.
Nevertheless - here is all the relevant code:
The Registration in the ViewModelLocator:
public ViewModelLocator()
{
[...]
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
Get the Data from a Database:
Invokation:
public sealed partial class MainPage : Stundenplaner.Common.LayoutAwarePage
{
MainViewModel mainViewModel = new MainViewModel();
[...]
protected override void OnNavigatedTo(NavigationEventArgs e)
{
mainViewModel.GetSubjects();
base.OnNavigatedTo(e);
}
}
GetSubjects method and the ObservableCollection
public MainViewModel()
{
_Subjects = new ObservableCollection<SubjectViewModel>();
}
public const string SubjectsPropertyName = "Subjects";
private ObservableCollection<SubjectViewModel> _Subjects = null;
public ObservableCollection<SubjectViewModel> Subjects
{
get
{
return _Subjects;
}
set
{
if (_Subjects == value)
{
return;
}
RaisePropertyChanging(SubjectsPropertyName);
_Subjects = value;
RaisePropertyChanged(SubjectsPropertyName);
}
}
public void GetSubjects()
{
using (var db = new SQLite.SQLiteConnection(App.DBPath))
{
var query = db.Table<Subject>().OrderBy(c => c.Name);
foreach (var _subject in query)
{
var subject = new SubjectViewModel()
{
Id = _subject.Id,
Name = _subject.Name
};
_Subjects.Add(subject);
}
}
}
The Binding to that Collection:
<GridView ItemsSource="{Binding Main.Subjects, Source={StaticResource Locator}}" [...]/>
EDIT
Thanks to Rohit Vats I've solved the problem now:
Insted of creating a new instance of the MainViewModel I've created an instance of the ViewModelLocator that accesses the registered instance of the MainViewModel like so:
ViewModelLocator Vml = new ViewModelLocator();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Vml.Main.GetSubjects();
base.OnNavigatedTo(e);
}
MainWindowViewModel instances are different that's why you database data not visible on GUI.
GridView is binded to Main -
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
ServiceLocator.Current.GetInstance<MainViewModel>(); will return new instance of MainWindowViewModel.
Whereas while navigating you are creating altogether new instance of MainWindowViewModel in MainPage and calling GetSubjects() on that instance.
You should create a single instance for MainWindowViewModel which will be shared between your View and MainPage.

Categories