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.
Related
There is a page where the user selects parameters to show the proper collection then on button click jumps to the next page (Coll) where it should show up.
User Selection Page XAML:
<ContentPage.BindingContext><xyz:UserSelectionViewModel</ContentPage.BindingContext>
...
<Button x:Name="Start" Command="{Binding LoadData}" Pressed="StartClick"/>
User Selection Page C#:
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (CollViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
...
vm.Subject = vm.Subject.ToLower();
}
UserSelectionViewModel:
public class UserSelectionViewModel : BaseViewModel
{
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
_pageService = DependencyService.Get<IPageService>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
FilteredData.AddRange(filtereddata);
OnPropertyChanged("FilteredData");
Debug.WriteLine(FilteredData.Count());
await Device.InvokeOnMainThreadAsync(() => _pageService.PushAsync(new Coll(FilteredData)));
}
}
Coll XAML:
<ContentPage.BindingContext><xyz:CollViewModel</ContentPage.BindingContext>
...
<CarouselView ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type z:Coll}}, Path=InheritedData}" ItemTemplate="{StaticResource CollTemplateSelector}">
...
Coll C#:
public partial class Coll : ContentPage
{
public ObservableRangeCollection<Feladatok> InheritedData { get; set; }
public Coll(ObservableRangeCollection<Feladatok> x)
{
InitializeComponent();
InheritedData = x;
OnPropertyChanged(nameof(InheritedData));
}
}
CollViewModel:
public class CollViewModel : UserSelectionViewModel { ... }
BaseViewModel:
private ObservableRangeCollection<Feladatok> inheriteddata;
public ObservableRangeCollection<Feladatok> InheritedData
{
get
{
return inheriteddata;
}
set
{
if (value != inheriteddata)
{
inheriteddata = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InheritedData"));
}
}
}
Managed to make it work like this with the help of Jason's tips. My only concern remaining is that: Won't this slow down the page that I load the observable collection two times basically? Is it a good practice as I have made it?
Eventually set the BindingContext to the VM and Binding from there. I still feel like it could be done more efficently or maybe that's how it is done. ViewModels are still new for me and I feel like it's much more code and slower with it. But I will close this, as it is working now.
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);
}
}
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;
}
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}">
I have a observable collection of type Project, that I want to be displayed in a ListView but nothing is added to my ListView which I really dont understand
My MainWindow.xaml
<ListView Name="ListViewProjects" Grid.Column="0" Grid.RowSpan="3" SelectionChanged="ListViewProjectsSelectionChanged" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" MinWidth="100">
<ListView.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Path=ProjectID}"/>
<TextBlock Text="{Binding Path=ProjectName}"/>
</WrapPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
My MainWindow.cs
public partial class MainWindow : Window
{
ObservableCollection<Project> Projects = new ObservableCollection<Project>();
ObservableCollection<Employee> Employees = new ObservableCollection<Employee>();
public MainWindow()
{
InitializeComponent();
DataContext = Projects;
Project pro1 = new Project(1, "Swordfish");
Projects.Add(pro1);
Employee empMads = new Employee("Mads", 1);
Employee empBrian = new Employee("Brian", 2);
Employees.Add(empMads);
Employees.Add(empBrian);
}
private void ListViewProjectsSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
and my Project.cs which is the class file
[Serializable]
class Project : INotifyPropertyChanged
{
public Project(int id, string name)
{
ID = id;
Name = name;
}
private int id;
public int ID
{
get { return id; }
set
{
id = value;
NotifyPropertyChanged("ProjectID");
}
}
private string name;
public string Name
{
get { return name; }
set
{
name = value;
NotifyPropertyChanged("ProjectName");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
But nothing is added to my list I cant see what I am missing for it to work.
I have it as a observablecollection
I do DataContext = the collection
And I do binding in the xaml file
Edit codepart 1:
public ObservableCollection<Project> Projects { get; set; }
public ObservableCollection<Employee> Employees { get; set; }
public MainWindow()
{
InitializeComponent();
Projects = new ObservableCollection<Project>();
Employees = new ObservableCollection<Employee>();
DataContext = Projects;
It's because you use binding path like ProjectID while your Project class has property ID. The same goes for ProjectName and Name properties.
E:
When you have problems with binding it's very useful to look through Output tab in visual studio's debug mode to see what errors were returned from databinding engine. Those exceptions are normally not returned to user, but you can inspect them there.
As pointed out, there are a few problems here. One of the "gotchas" that get you as a new WPF developer is that you can't use member variables, you have to use public properties:
public ObservableCollection<Project> Projects { get; set; }
public ObservableCollection<Employee> Employees { get; set; }
These, of course, need to be initialized in your constructor.
Once you do that, you need to make sure the properties of the collection match what you're binding to on the ListView.
By default your member variables are going to be private. If what you have posted here is your actual code, then the XAML has no way of accessing it. Makes those properties public:
public ObservableCollection<Project> Projects = new ObservableCollection<Project>();
public ObservableCollection<Employee> Employees = new ObservableCollection<Employee>();
1.Your list needs to be a public property (Edit: unless you set the DataContext like you did...) (public ObservableCollection<Project> Projects {get;})
2.Your notification should be the same as the actual property name:
private int id;
public int ID
{
get { return id; }
set
{
id = value;
NotifyPropertyChanged("ID");
}
}
3.The binding needs to be that way as well:
<TextBlock Text="{Binding Path=ID}"/>
I think...