I am trying to build an mvvm app using mvvmcross. When I start the app a null reference exception occurs.
this is my codebehind file which uses MvvmCross.WindowsUWP.Views.
public sealed partial class MainView : MvxWindowsPage
{
public MainView()
{
this.InitializeComponent();
MainViewModel = (MainViewModel)ViewModel;
}
public MainViewModel MainViewModel { get; set; }
public PlayersViewModel PlayersViewModel { get; set; } = Mvx.IocConstruct<PlayersViewModel>();
}
This is my app.cs file.
public class App : MvxApplication
{
public override void Initialize()
{
RegisterAppStart<MainViewModel>();
}
}
When I set an break point at MainViewModel = (MainViewModel)ViewModel; the break point is hit and I can see that the ViewModel property is null. what I am doing wrong? Thanks in advance.
ViewModel property is not yet initialized in the constructor. That's why its value is still null.
You will need to move the assignment to a different method that gets called later, e.g. OnNavigatedTo:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
MainViewModel = (MainViewModel)ViewModel;
}
Even better, instead of assigning ViewModel to your own auto implemented property, rather have the MainViewModel getter perform the cast. Then you don't even need to do the assignment:
public MainViewModel MainViewModel => (MainViewModel)ViewModel;
Unfortunatelly UWP doesn't support generic base classes in XAML, otherwise you could use MvxWindowsPage<MainViewModel> as the base page, not needing to do the cast at all.
Related
My app is a translation app. It contains a translation list that is passed to different viewmodel. Those viewmodels migth modify those lists including add and remove operations. For this purpose, I convert this list to an ObservableCollection in the constructor and my list is no longer modified. I know converting to an ObservableCollection creates a new object and the references are no longer the same. It is working perfectly for the concerned view, but once I want to change to another view, the list isn't updated. I was wondering what was the best way to solve this problem?
I thought I could create a custom ObservableCollection that would contain the corresponding list and automatically update it when an add or remove operation would be done. Something that'd look similar to this.
View
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// this is where I instanciate the viewModel, and the
// list<Translation> isn't modify once I close the view
DataContext = new ModifyWordVM(translations);
}
}
ViewModel
public class ModifyWordVM: INotifyPropertyChanged
{
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get { return translations; }
set { translations = value; OnPropertyChanged("Translations"); }
}
public ModifyWordVM(List<Translation> translations)
{
// Converting list to ObservableCollection
Translations = ConvertionHelper.ConvertTo(translations);
}
}
I'd like to know what is the cleaner way to get the modified list back.
You should encapsulate the traslations and their operations. To do this just introduce a class e.g. TranslationService which is shared between all relevant view models. To omit a smelly Singleton I added an instance of the service to the App.xaml resources.
The idea is that all modifications of the translation list take place in one location or type. The same type that is the binding source for the view. When adding a new translation the view should invoke a ICommand on the view model. This command will invoke the AddTranslation method on the TranslationService. Same for remove. Any changes to the translation collection will now reflect across the application.
If you also want to catch modifications of the actual translations (e.g. rename or edit) the TranslationService need to handle the PropertyChanged event of the ObservableCollection items as well.
When an items property changed the TranslationService must respond by raising the PropertyChanged event for the ObservableCollection property Translations. This would require the items to implement INotifyPropertyChanged too.
App.xaml
Shared TranslationService instance
<Application.Resources>
<TranslationService x:Key="TranslationService">
<TranslationService.DatabaseService>
<DatabaseService />
</TranslationService.DatabaseService>
</TranslationService>
</Application.Resources>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private void ListViewItem_PreviewMouseDown(objectsender,MouseButtonEventArgs e)
{
// Instantiate the view model and initialize DataContext from XAML instead.
// This method became redundant.
}
}
MainWindow.xaml
<Window.DataContext>
<ModifyWordVM>
<ModifyWordVM.TranslationService>
<!-- Reference the shared instance -->
<StaticResource ResourceKey="TranslationService" />
</ModifyWordVM.TranslationService>
</ModifyWordVM>
</Window.DataContext>
ModifyWordVM.cs
public class ModifyWordVM: INotifyPropertyChanged
{
public ModifyWordVM()
{}
public AddTranslation(Translation translation) => this.translationService.AddTranslation(translation);
public RemoveTranslation(Translation translation) => this.translationService.RemoveTranslation(translation);
public TranslationService TranslationService {get; set;}
public ObservableCollection<TranslationVM> Translations => this.translationService.Translations;
}
TranslationService.cs
public class TranslationService
{
public TranslationService()
{}
public AddTranslation(Translation translation)
{
// Add translations
}
public RemoveTranslation(Translation translation)
{
// Remove translations
}
private DatabaseService databaseService;
public DatabaseService DatabaseService
{
get => this.databaseService;
set
{
this.databaseService = value;
this.Translations = databaseService.getTranslations;
}
}
private ObservableCollection<TranslationVM> translations;
public ObservableCollection<TranslationVM> Translations
{
get => this.translations;
set
{
this.translations = value;
OnPropertyChanged("Translations");
}
}
}
I want to update textblock1 to 'there' from a class other than the main. As shown this code gives no errors, but does not work. Textblock1 is set to
FieldModifier="Public".
namespace myProgram
{
public sealed partial class MainPage : Page
{
Library stuff = new Library();
public MainPage()
{
this.InitializeComponent();
}
public void mainStuff()
{
stuff.Doit(new MainPage());
}
}
}
namespace myProgram
{
public class Library
{
public void Doit(MainPage mainPage)
{
mainPage.textblock1.Text = "there";
}
}
}
The short answer
Pass your Window as a parameter to the other class.
public class Library {
public void Doit(MainPage mainPage)
{
mainPage.textblock1.Text = "there";
}
}
EDIT According to the other answer that is posted here, you can't (by default) access controls from outside the class (as they are set to protected).
You can override the access modifier to public (refer to the other answer), but that seems to violate the idea that UI and data logic should be separated.
It does work, from a technical point of view; I just suggest not doing it because it can lead to future problems in development.
The cautionary answer
You should endeavour to keep your UI and data logic separate. In most cases where you want another class to access your window's controls; you are violating the principle of UI and data logic segregation.
In short, you don't want anyone (except MainPage) to be aware that a Mainpage has a Textblock; let alone giving them the ability to set its content directly.
In MainPage, create a public property:
public String MyDisplayValue
{
get
{
return this.textblock1.Text;
}
set
{
this.textblock1.Text = value;
}
}
In your external Library class:
public void Doit(MainPage mainPage)
{
mainPage.MyDisplayValue = "there";
}
Functionally speaking, the application works the same as in the short answer I supplied. But this one creates a better separation between UI and data logic.
This is a matter of good practice, not technical requirement.
Make you TextBlock control public like this. x:FieldModifier="public"
<TextBlock Name="TextBlockName"
x:FieldModifier="public"/>
Then expose you MainPage class
public sealed partial class MainPage : Page
{
public static MainPage mainPage { get; set; }
public MainPage()
{
this.InitializeComponent();
mainPage = this;
}
}
then in our Library class do this:
public class Library
{
private TextBlock myTb{ get; set; }
public Library()
{
myTb = MainPage.mainPage.TextBlockName;
}
public void Doit()
{
myTb.Text = "there";
}
}
I’m having a weird & frustrating problem passing an object between some of my classes. This stems from the fact I am a scripter and not a programmer, and am just bumbling along. So I’m sure I’m doing something dumb :)
I am trying to build a Wizard dialog which has multiple pages. I am using the “Internationalised WPF Wizard” tutorial from CodeProject as a starting point, and attempting to adapt it to my domain. I’m getting stuck because my wizard pages can’t seem to refer to the model.
I have done the following:
Created a class for my Model (let call this MyData)
Created a base class for my view models (ViewModelBase)
Created a view model class for each of my pages, inheriting from ViewModelBase (example below is WelcomePageViewModel)
Created a ‘controller’ style view model which drives the wizard. (WizardController)
When the wizard is launched, WizardController is instantiated. WizardController also instantiates MyData.Then, WizardController instantiates each of the view models for the remaining pages.
The actual GUI seems to work fine, and I can see that the view models for each of the pages are being loaded correctly. Here’s some code:
public class MyData
{
private string _someString;
public MyData(string someString)
{
_someString = someString;
}
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
readonly MyData _myData;
public ViewModelBase(MyData myData)
{
_myData = myData;
}
}
public class WizardController : INotifyPropertyChanged
{
MyData _myData;
public WizardController()
{
_myData = new MyData("The Widgets");
}
}
public class WelcomePageViewModel : ViewModelBase
{
private MyData _myData;
public WelcomePageViewModel(MyData myData)
: base(myData)
{
_myData = myData;
// accessing _myData fails :(
MyLogger.WriteLine("Grabbed an instance of myData: " + _myData.ToString());
}
}
However, my code fails when I try to access myData from WelcomePageViewModel. On the MyLogger line in WelcomePageViewModel, the error “Object reference not set to an instance of an object.” is thrown.
Basically, all I’m trying to achieve is WizardController setting up MyData, and each of the wizard pages being able to access (and manipulate) it. So any guidance would be greatly appreciated!
As Rob G suggested in a comment, you're re-declaring the variable _myData in the inherited classes. The correct way to organize your code is to let the _myData be a protected property declared only on the abstract base class, and use this property to access the variable from the inheriting classes.
public abstract class ViewModelBase : INotifyPropertyChanged
{
bool _isCurrentPage;
protected MyData MyData { get; private set; }
public ViewModelBase(MyData myData)
{
MyData = myData;
}
}
public class WelcomePageViewModel : ViewModelBase
{
public WelcomePageViewModel(MyData myData)
: base(myData)
{
// Access the protected property
MyLogger.WriteLine("Grabbed an instance of myData: " + MyData.ToString());
}
}
Edit: fixed a copy-paste error...
In my prism application I want to make a single shared instance of a view. When I try to navigate the first time it works fine, but when I try to second time it's not working. If I change the PartCreationPolicy from Shared to NonShared it works but it's give me a new instance. Are there any options for another way to do this?
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl
{
public AppMain()
{
InitializeComponent();
}
}
You might want to play around with Prism's KeepAlive value for your view. This value determines whether the view should be removed from the region when you navigate away from it. You have two ways of doing this:
Using the RegionMemberLifetime attribute
[RegionMemberLifetime(KeepAlive = false)]
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl
{
public AppMain()
{
InitializeComponent();
}
}
Implementing the IRegionMemberLifetime interface
[Export(ViewNames.AppView)]
[PartCreationPolicy(CreationPolicy.Shared)]
public partial class AppMain : UserControl, IRegionMemberLifetime
{
public AppMain()
{
InitializeComponent();
}
public bool KeepAlive
{
get { return false; }
}
}
You can read some more about the KeepAlive property here.
If I try "var mainpage new Mainpage()"
I will run the mainpage constructor and then all the fields in the XAML object will return to null. How to I access XAML objects in silverlight that are from a different class but part of the same namespace?
Let me explain by example. If you look at the first answer, here is what I am encountering
public class MyPage
{
MyPage()
{
// the constructor makes all the variables from the xaml null
}
public TextBox MyTextBox
{
get { return SomeTextBox; }
}
}
public class SomeOtherClass
{
private void SomeFunction()
{
var page = new MyPage(); // this makes the text empty
var sometext = page.MyTextBox.Text; // so sometext will be empty
}
}
So whatever the user imputs when the program first runs turns to null when I run SomeFunction.
What I am first going to try is to see if when SomeClass is created, the values are put into that class.
If that fails, I am going to try MVVM. I have seen the http://www.vimeo.com/8915487 video and I got the sample mvvm code
Here is the Model:
namespace SimpleMVVM.Model
{
public class SimpleModel
{
// super easy version
//public string SomeSimpleValue { get; set; }
private string _SomeSimpleValue = string.Empty;
// actually do something version...
public string SomeSimpleValue
{
get
{
return "some value";
}
set
{
_SomeSimpleValue = value;
}
}
}
}
here is the view:
and here is the viewmodel.cs
using Simple;
using SimpleMVVM.Model;
namespace SimpleMVVM.ViewModel
{
public class SimpleViewModel : SimpleViewModelBase
{
private SimpleModel MyModel = new SimpleModel();
public string SomeSimpleValue
{
get { return MyModel.SomeSimpleValue; }
set
{
if (MyModel.SomeSimpleValue != value)
{
MyModel.SomeSimpleValue = value;
RaisePropertyChanged("SomeSimpleValue");
}
}
}
}
}
Using this example, I am wondering if it will just as easy as injecting a ViewModel and then changing the bindings in the Model and the View.
Is MVVM really this easy?
There is one more. It is the viewmodel base class
using System.ComponentModel;
namespace Simple
{
public class SimpleViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var e = new PropertyChangedEventArgs(PropertyName);
PropertyChangedEventHandler changed = PropertyChanged;
if (changed != null) changed(this, e);
}
}
}
OK, so now the hard part. If I create a new class. How do I get the data from the viewmodel class?
First, let me get this rant out of the way: what you propose is very bad design. It fits the definition of smelly code.
If you insist on doing it this way, the "best" approach to take is to declare some public variables on your page that return the actual UI elements.
<UserControl x:Class="MyNamespace.MyPage" ...>
<Grid>
<TextBox x:Name="SomeTextBox" Width="100" />
</Grid>
</UserControl>
public class MyPage
{
public TextBox MyTextBox
{
get { return SomeTextBox; }
}
}
public class SomeOtherClass
{
private void SomeFunction()
{
var page = new MyPage();
page.MyTextBox.Text = "some text";
}
}
Of course the preferred method would be to use something like the MVVM pattern to implement binding from your window to its viewmodel, then you can just read the property values from the viewmodel, this way you avoid trying to touch any UI elements from a totally different class.
Another way to do it (without going the full MVVM route) is to inject the necessary values into the constructor of the control/page that you are instantiating, and from there you can assign them to the appropriate UI element properties. This is still smelly, but better than directly accessing the UI elements from the outside.