WPF component content not updating after view model change - c#

I have the following view model:
public class ViewModel : ObservableObject
{
public string Watermark { get; set; } = "Loading...";
}
where ObservableObject is from Microsoft.Toolkit.Mvvm.ComponentModel which implements INotifyPropertyChanged and INotifyPropertyChanging.
I have the following XAML:
<xctk:WatermarkTextBox>
<xctk:WatermarkTextBox.WatermarkTemplate>
<DataTemplate>
<ContentControl Content="{Binding DataContext.Watermark, RelativeSource={RelativeSource AncestorType=Window}}"></ContentControl>
</DataTemplate>
</xctk:WatermarkTextBox.WatermarkTemplate>
</xctk:WatermarkTextBox>
And lastly the following C# code:
private async void Window_Loaded(object sender, RoutedEventArgs e)
{
Dispatcher.Invoke(() =>
{
// Assigning property of textbox directly with .Watermark = "" does not work
// Updating view model property doesn't work either
Model.Watermark = "Done";
});
}
When the window loaded method gets called however, the watermark of the watermark textbox does not update to reflect its supposed new value, "Done".
It might be a lack of general WPF understanding but if anyone could give a pointer as to why this might fail I would greatly appreciate it.
Thanks!

You aren't using ObservableObject properly. It's not magic (unlike some solutions that use frameworks such as Fody), so you still have to do the work yourself to propagate changes. Note the example in the documentation.
public class User : ObservableObject
{
private string name;
public string Name
{
get => name;
set => SetProperty(ref name, value);
}
}
So you'll have to follow that pattern:
public class ViewModel : ObservableObject
{
private string watermark;
public ViewModel()
{
Watermark = "Loading...";
}
public string Watermark
{
get => watermark;
set => SetProperty(ref watermark, value);
}
}

Related

Values of View don't update after changing data

I'm trying to build an application that collects information of dams and weather forecasts from api and shows the data to users.
As shown below, the main page is supposed to look similar for dam information and weather forcast information.
Blue print
So, I want to make a component for card, that I can reuse for the data. In other words, I want to put 1) mainCards into 2)mainPage, so that I can have a page full of cards. And then, I want to put the mainPage on top of 3)mainWindow.
I want to change the values inside of the purple cards when I click a menu button on the left side that says "Dam Info" and "Weather Info".
When I press the "Dam Info" button, I want the purple cards to fetch dam data from an API and show it on the screen. Same for "Weather Info" button.
The problem is, I can clearly see that the values that I binded to the text blocks in the cards change when I run the code and show them on console. But, the actual UI doesn't update the change.
INotifyProperty says that the view model I implement for the current view changes as I press a button. It also says that the values(that I binded to the textblocks for purple cards) in a view model for purple card components change when I press a button.
I implemented a base class that implemented INotifyPropertyChanged for all view models, and RelayCommand for all commands.
I assumed that with INotifyPropertyChanged, the view will automatically change the UI value, but it does not.
I already tried to set the mode for binding to Two Way , but it doesn't work.
I tried to set certain values in a view model for Main Page(page that contains a lot of purple card components) and then make the purple card view model to fetch and reflect it. But it doesn't work.
Please help me. I've been dealing with this problem for a week. I have not much time left to just struggle with changing views and their components. I'm new to MVVM pattern and WPF and completely lost.
xaml of Main window is shown below
<Grid>
<RadioButton Content="Dam Info"
Command="{Binding ChangeMainPageForDamCommand}"
CommandParameter="Dam"/>
<RadioButton Content="Weather Info"
Command="{Binding ChangeMainPageForWeatherCommand}"
CommandParameter="Weather"/>
<RadioButton Content="My data"
Command="{Binding DetailPageCommand}"/>
<ContentControl Content="{Binding CurrentView,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
view model for main window is shown below
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public RelayCommand MainPageCommand { get; set; }
public RelayCommand DetailPageCommand { get; set; }
public RelayCommand ChangeMainPageForDamCommand { get; set; }
public RelayCommand ChangeMainPageForWeatherCommand { get; set; }
public MainPageViewModel DamMainPageVM { get; set; }
public MainPageViewModel WeatherMainPageVM { get; set; }
public MainPageViewModel MainPageVM { get; set; }
public DetailPageViewModel DetailPageVM { get; set; }
public MainWindowViewModel()
{
MainPageVM = new MainPageViewModel("Dam");
CurrentView = MainPageVM;
DetailPageVM = new DetailPageViewModel();
ChangeMainPageForDamCommand = new RelayCommand(o =>
{
CurrentView = null;
DamMainPageVM = new MainPageViewModel("Dam");
CurrentView = DamMainPageVM;
Debug.WriteLine("ChangeMainPageFor Dam Command executed");
});
ChangeMainPageForWeatherCommand = new RelayCommand(o =>
{
WeatherMainPageVM = new MainPageViewModel("Weather");
CurrentView = WeatherMainPageVM;
Debug.WriteLine("ChangeMainPageFor Weather Command executed");
});
DetailPageCommand = new RelayCommand(o =>
{
CurrentView = DetailPageVM;
});
}
xaml for mainPage that contains cards is shown below
<Grid>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
<components:MainCard/>
</Grid>
view model for the main page is shown below
public MainCardDatum MainCardDatumItem = new MainCardDatum();
public RelayCommand GetDamDetailCommand { get; set; }
private MainCardViewModel _mainCardViewModel;
public MainCardViewModel MainCardViewModel
{
get
{
return _mainCardViewModel;
}
set
{
_mainCardViewModel = value;
OnPropertyChanged(nameof(MainCardViewModel));
}
}
public MainPageViewModel() { }
public MainPageViewModel(string pageName)
{
var d = new MainCardViewModel(pageName);
GetMainPage(pageName);
Debug.WriteLine(pageName+" is ON");
}
public void GetMainPage(string pageName)
{
GenerateMainCard(pageName);
Debug.WriteLine(pageName+" made the page.");
}
private void GenerateMainCard(string pageName)
{
if (pageName == "Dam")
{
MainCardDatumItem.Id = 1;
MainCardDatumItem.MainCardName = "damDummyInfoName";
MainCardDatumItem.MainCardOption = "damDummyInfoOption";
}
else if (pageName == "Weather")
{
MainCardDatumItem.Id = 1;
MainCardDatumItem.MainCardName = "weatherDummyInfoName";
MainCardDatumItem.MainCardOption = "weatherDummyInfoOption";
}
else
{
Debug.Write("What?");
}
}
Main Card(Purple card component) xaml is below
<Grid>
<StackPanel>
<TextBlock Name="nameHehe"
Text="{Binding MainCardName,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Name="optionHehe"
Text="{Binding MainCardOption}"/>
</StackPanel>
<Button Background="Transparent"
Command="{Binding GetDamDetailCommand}"/>
</Grid>
Finally, the view model for the main card(purple card component) is below
public MainPageViewModel MainPageVM = new MainPageViewModel();
private string _mainCardName;
public string MainCardName
{
get
{
return _mainCardName;
}
set
{
_mainCardName = value;
OnPropertyChanged(nameof(MainCardName));
}
}
private string _mainCardOption;
public string MainCardOption
{
get
{
return _mainCardOption;
}
set
{
_mainCardOption = value;
OnPropertyChanged(nameof(MainCardOption));
}
}
public RelayCommand ClickCommand { get; set; }
public RelayCommand GetDamDetailCommand { get; set; }
public RelayCommand GetWeatherDetailCommand { get; set; }
public MainCardViewModel()
{
GenerateCardsForDam();
GetDamDetailCommand = new RelayCommand(o =>
{
MainCardName = "changed1";
MainCardOption = "changed2";
});
}
public MainCardViewModel(string pageName)
{
if (pageName == "Dam")
GenerateCardsForDam();
else
GenerateCardsForWeather();
GetDamDetailCommand = new RelayCommand(o =>
{
GenerateCardsForWeather();
});
}
public void GenerateCardsForDam()
{
MainCardName = "DamName1";
MainCardOption = "DamOption1";
Debug.WriteLine("GenerateCardsForDam executed");
}
public void GenerateCardsForWeather()
{
MainCardName = "WeatherName1";
MainCardOption = "WeatherOption1";
Debug.WriteLine("GenerateCardsForWeather executed");
}
I deleted out all unnecessary codes that are not related to this problem in xaml.
What am I doing wrong? Please help.
Also, it would be wonderful to know any discord server that I can ask and answer questions related to programming. I'm having a hard time to explain this problem in text tbh. Please let me know if you know one.
Thank you so much for reading so far. Sorry for messy explanation.
https://github.com/yk170901/dnw.git
Above is my Github repository for the project. I tried to add it correctly, but somehow the project folder become empty when I try to upload it. So I added a zip, I hope you can see the problem with the zip file. Sorry for the inconvenience.
You defined a DataTemplate for MainPage (a UserControl) in App.xaml.
<DataTemplate DataType="{x:Type viewModel:MainPageViewModel}">
<view:MainPage/>
</DataTemplate>
When this DataTemplate is applied, it is expected to instantiate a MainPage and set the instance of MainPageViewModel to MainPage's DataContext.
However, you added a code block shown below in MainPage.xaml.
<UserControl.DataContext>
<viewModels:MainPageViewModel/>
</UserControl.DataContext>
This means that when a MainPage is instantiated, an instance of MainPageViewModel will be instantiated as well and that instance will be set to MainPage's DataContext. This seems to prevent the DataTemplate from working as expected.
So, the solution is to remove that code block in MainPage.xaml.
In addition, I found that the appearace of MainPage will be the same regardless of the instance of MainPageViewModel and so you will not be able to check the difference from its appearance.

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;
}

Trouble binding to a textblock - Windows Phone - MVVM

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}">

Issue with databinding to class?

I'm having an issue with databinding a textblock to a custom prop inside another class, what am I doing wrong?
mainpage:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
InitializeLanguage();
}
private void InitializeLanguage()
{
LanguageHelper lh = new LanguageHelper();
// this.TitlePanel.DataContext = lh;
txtTitle.DataContext = lh;
}
}
databinding:
<TextBlock x:Name="txtTitle"
Text="{Binding homepage_subheading}"
Style="{StaticResource PhoneTextNormalStyle}"
Foreground="White"
Margin="12,0"/>
LanguageHelper class:
public class LanguageHelper
{
public String homepage_subheading;
public void changeLanguage()
{
if (true)
{
//english
homepage_subheading = "This is the top / sub Heading";
}
}
}
You don't have a property but a public field, and the databinding engine only works on properties.
So you need to change your class:
public String homepage_subheading { get; set; }
If you want to also notify the UI with your changes of your properties your LanguageHelper should implement the INotifyPropertyChanged interface and fire the PropertyChange event when you modify your properties.
You should declare some dependency property or use INotifyPropertyChanged, I would like to use a dependency property:
public class LanguageHelper : DependencyObject {
public static DependencyProperty Hompage_subheadingProperty =
DependencyProperty.Register("Homepage_subheading", typeof(string), typeof(LanguageHelper));
public String Homepage_subheading {
get { return (string) GetValue(Homepage_subheadingProperty);}
set { SetValue(Homepage_subheadingProperty, value);}
}
}
Note about the naming convention in C#, all properties should have first letter capitalized.

Categories