WPF: navigating between user controls resets viewmodel - c#

I have an application where I am using usercontrols as "pages" of the application. I have a currentpage binding in ApplicationViewModel on my MainWindow, and I navigate between pages by changing the binding of currentpage with commands attached to a side menu control. I am using the MVVM pattern and all of my ViewModels derive from a BaseViewModel class.
The navigation works, but when I input text into a text box, then navigate away and then back, the user-input text is reset to it's default binding.
I've already tried updating the source trigger and setting the mode to TwoWay. My "page" has a viewmodel which it is bound to, and otherwise works.
On my page, in the parent grid all controls are in:
DataContext="{x:Static core:MyPageViewModel.Instance}">
And the control:
<TextBox Text="{Binding TextBoxTest, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
And in my viewmodel:
public static MyPageViewModel Instance => new MyPageViewModel();
public string TextBoxTest { get; set; } = "Change Me!";
I would like the value I enter to remain when I navigate away, and then return, to the page. I assume it's because when I navigate away from my usercontrol I'm unloading it, and when I navigate back I'm getting a new instance of the viewmodel. I just don't know how to keep a single one that remains in memory.

You should post more code, it's not clear from the pieces.
I can try to guess anyway the problem is here:
public static MyPageViewModel Instance => new MyPageViewModel();
This generates a new ViewModel every time it is accessed by your view, because it is the equivalent of writing:
public static MyPageViewModel Instance { get { return new MyPageViewModel(); } }
instead, you should write something like
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
This way, the first time it is accessed, it returns the default value (new MyPageViewModel()), and now that static variable will always point to the same view model, instead of creating a new one.

Guido C. was exactly correct. I changed my viewmodel instance from the way I had it in my question to this:
public static MyPageViewModel Instance { get; } = new MyPageViewModel();
And it worked.

Related

How to bind C# generated button to XAML MVVM

So far I have read a lot about binding and I have looked up my question. Unfortunately the questions I stumbled upon, were questions regarding the CONTENT of a button.
My question rather is:
How can I bind a C# generated button (new Button (//CONTENT)) to my XAML, rather then just binding content to my already generated button?
in my MVVM code I have property which holds a button as value called RequestedButton, but I have no idea as to what kind of tag I should use in my XAML to bind this property to.
I do know how to bind this if it were to be a ObservableCollection, but no idea on how to bind it if it's a single attribute.
So my question (to the point) is: What kind of element should I use in my XAML, to use the binding on for the property RequestedButton, to use {Binding RequestedButton} on?
In my class I have a property named RequestedButton, which holds a Button as a value. Like this:
Class Foo
{
public Button RequestedButton {get; set;}
public Foo()
{
RequestedButton = new Button()//GENERATE BUTTON WITH PROPERTIES IN IT
}
}
What tag should I now use, to correctly display (use Binding) the button ABOVE in my XAML?
View Model
private Button requestedButton = new Button();
public Button RequestedButton
{
get
{
return requestedButton;
}
set
{
requestedButton = value;
NotifyPropertyChanged(nameof(RequestedButton));
}
}
...
Xaml
<ContentView Content="{Binding RequestedButton}"/>

What is the proper way to create dependency properties in user controls that allow two way bindings

I say proper because all the examples I have seen all seem to contradict each other and or fall short in some respects. I need to be able to bind a class object to my DP from XAML or set it programmatically in cb:
<local:MyControl MyDP="{Binding MyObject}"/>
or
MyControl mycontrol = new MyControl(){MyDP = MyObject};
and then within the control, two way bind elements to properties of the binding object:
<TextBox text="{Binding MyDP.text, ElementName=MyControl}"/>
I would think this is pretty standard stuff, but the lack of cohesion I have seen in people trying to write examples has led me to believe otherwise.
This is how I do it:
Assume a parent control that contains a status bar (User Control) it's markup looks like this:
<ContentControl x:Name="XFoot" Grid.Row="1" Grid.ColumnSpan="3" >
<UserControls:UCStatusBar />
</ContentControl>
Now in the UserControl named status bar it's dependency property looks like this:
public StatusUpdate CurrentStatus
{
get { return (StatusUpdate)GetValue(CurrentStatusProperty); }
set { SetValue(CurrentStatusProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentStatus. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CurrentStatusProperty =
DependencyProperty.Register("CurrentStatus", typeof(StatusUpdate), typeof(UCStatusBar),
new PropertyMetadata(
new StatusUpdate() { ErrorMessage="", IsIndeterminate=false, Status="Ready"}
)
);
Status Update looks like this, it's just a container for three properties shown in the status bar.
public class StatusUpdate
{
public StatusUpdate()
{
Status = "";
ErrorMessage = "";
IsIndeterminate = true;
}
public string Status { get; set; }
public string ErrorMessage { get; set; }
public bool IsIndeterminate { get; set; }
}
}
Anyone can update the status bar by accessing the CurrentStatus property of the statusbar. NOTE to make a two way binding, that's done in XAML... within the binding directive, just press space bar and the property window will show binding modes. Pick two way.
PS: In Visual Studio, when you first create the DP just type in propdp and press the tab button, the entire DP structure is inserted for you automatically. As a result DPs are easy to implement.
How do DPs work two-way?
If you are using XAML binding you simply tell it via the MODE property that it's two way. This means that the GUI changes will update the properties when user changes the values.
<TextBox Text="{Binding Path=ThePropertyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Notice the Mode value and the UpdateSourceTrigger says, don't want for focus to change, update it right away.
Wait a minute, nothing is happening when I bind and make changes!
Three things are required for a binding to work 1) DataContext must be set either in code behind or as a static resource in the XAML 2) The pathname to the property name must exactly the CLR property name and 3) There must be content in the property.
Can I fire an Event from somewhere else to update the property?
Sure... the first step is to set up the static eventhandler in the UserControl like this:
public static EventHandler<string> NewData;
Then in the CTOR wire it up like this:
NewData+=OnNewData;
Then the event handler looks like this:
private void OnNewData(object sender, string data){
//setting this string property notifies WPF to update the GUI
ThePropertyName = data;
}
The other code does this...
MyUserControl.OnNewData(this, "Now is the time for all good men to...");

Issues with INavigationAware in Prism 5

I'm fairly new to WPF and MVVM with Prism and I'm having an issue with Prism navigation. Each time I navigate to a particular view, I want to load a new view; however, if I enter some data or fully process some data, navigate away and come back, the existing data is always kept in the view.
I have read similar issues here on SO such as this and this. Both point to using INavigationAware or IRegionMemberLifetime. I have tried to implement both in my ViewModel; however, none seem to solve the issue for me.
If I use INavigationAware and set IsNavigationTarget to false, the view never loads. If I use IRegionMemberLifetime and set KeepAlive to false, it still retains my data.
I could post code; however, it's simple and looks like the same that's in the linked issues. Has anyone had similar issues with Prism navigation?
Any help is appreciated.
Edit - Adding sample code
In an effort to get rid of any red herrings with other code I've got, I created a new Prism app with the bare necessities. My issue with the view not displaying when navigating back has cleared up; however, the view is still retaining the entered values.
Here is some sample code:
Model:
public class SomeObject
{
public string ObjectName { get; set; }
}
I created a basic view with just two TextBox controls, 1 bound and 1 not:
<StackPanel>
<TextBlock Text="ModuleA" />
<TextBox Text="{Binding DisplayedSomeObject.ObjectName, UpdateSourceTrigger=PropertyChanged}" />
<TextBox />
</StackPanel>
ViewModel:
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
[RegionMemberLifetime(KeepAlive=false)]
public class ModuleAViewModel : BindableBase, IConfirmNavigationRequest, INavigationAware
{
private SomeObject displayedSomeObject = new SomeObject();
public SomeObject DisplayedSomeObject
{
get { return displayedSomeObject; }
set
{
displayedSomeObject = value;
}
}
[ImportingConstructor]
public ModuleAViewModel()
{
}
void IConfirmNavigationRequest.ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
{
continuationCallback(true);
}
bool INavigationAware.IsNavigationTarget(NavigationContext navigationContext)
{
return false;
}
void INavigationAware.OnNavigatedFrom(NavigationContext navigationContext)
{
}
void INavigationAware.OnNavigatedTo(NavigationContext navigationContext)
{
var newSomeObject = new SomeObject();
this.DisplayedSomeObject = newSomeObject;
}
}
When I run this, both the bound and un-bound controls retain their values when navigated back to.
Have you done some troubleshooting on Navigation? Set a breakpoint to when the page is navigated to and see how all the data is coming back in. I thought I had the same problem before and I was foolishly loading a object into my VM that was never getting destroyed.
Because of this, it appeared the VM was being kept alive, but in reality it was not. It would load like normal every time, but it would be pulling from the object that wasn't reloading.
So, set a breakpoint on the navigateTo and step through to see if the data is getting reloaded or not.
EDIT:
Looking at the code above, I believe you need to add IRegionMemberLifetime as well. I am currently using Prism 4.5, so I don't know if this has changed in 5, but I have to add that for it to actually destroy it.
public class ModuleAViewModel : BindableBase, IConfirmNavigationRequest, INavigationAware, IRegionMemberLifetime
{
...
bool IRegionMemberLifetime.KeepAlive
{
get { return false; }
}
}

Bind object properties to a datagrid in WPF

I have the following class:
public class Sp3dItem
{
public Sp3dItem()
{
Items= new ObservableCollection<Sp3dItem>();
}
public string OID
{
get;
set;
}
public string Name
{
get;
set;
}
public string Type
{
get;
set;
}
public ObservableCollection<Sp3dItem> Items
{
get;
set;
}
}
I need to show the properties of an instance of this object to a Datagrid (or any other type of grid). Like the Properties Window in Visual Studio. But there are certain properties that I don't care, like 'Items', I only need to show properties of string Type, and only the ones with non empty values (this last one would be a plus, not a real need).
The question is, can I do something like this with binding or do I have to assembly the data on the grid manually?
Sounds like you want a property grid to view the properties of a single object instance, where each property/value pair is a 'row', yes? If that's the case, look into some of the third-party Property Grid controls. The WPF Extended Toolkit has a free one.
Typically, these grids can automatically discover the properties of the target object, and you can choose to hide certain properties by adorning them with [Browsable(false)].
Yes... it's possible and easy once you figure out how the built-in binding wizard works.
This example is for a
<Label...
Create a static instance to your view model in the View. By doing this the designer will show the properties of the Viewmodel in the
properties page once you start "wiring up the bindings"...
//in code behind
public static string Error
{
get { return _Error; }
set { _Error = value; }
}
Now click on the XMAL component in designer just once.
<Label Grid.Row="2" <=Click here one time
In the properties page, click the icon (small square on far right side of property) to start the binding process
Select "Create Data Binding"
Select 'FindAncestor' then the MainWindow of interest, and finally the static property.
Click ok and the bindings are set in XAML Automatically.
<Label Grid.Row="2"
Content="{
Binding Error,
RelativeSource={
RelativeSource FindAncestor,
AncestorType={x:Type local:MainWindow}}}"/>
The verbosity above just says:
Look in MainWindow's static properties for Error.
Make this Label's content that value.
If you want you can also edit the template for the Datagrid, but that's not relevant to your question.

How can I open a page and assign its datacontext

my question is relatively easy, I guess.
I have a page to display my data. On a click on a button I want to open a new page with the datacontext of an element 2 layers above the datacontext of the current element.
Explanation:
My ViewModel is a class (ViewModelContainer) that contains more ViewModels. One is a summary of values and one is the detailed view of that.
public class SummaryViewModel
{
public int somevalue; // is a property
public ObservableCollection<SummarizedItems> items; // is a property
}
public class DetailsViewModel
{
public int someOthervalue; // is a property
public int stuffA; // is a property
public int stuffB; // is a property
}
public class ViewModelContainer : ViewModelBase
{
private SummaryViewModel _sum;
public SummaryViewModel sum { } // is a property
private DetailsViewModel _det;
public DetailsViewModel det { } // is a property
}
The View where I can press a button is bound to the value of the ObservableCollection of SummaryViewModel.
Everything is fine till now. When I press the button, a new page, showing the details should be opened. I use an ICommand to handle the click, and pass it the details view as a CommandParameter.
<Button Name="OpenDetailsButton" Command="{Binding Path=ACommand}" CommandParameter="{DynamicResource Details}"
I define a page as a resource in the same file, where the datacontext is still ViewModelContainer.
<pages:DetailsViewPage DataContext="{Binding Path=det }" x:Key="Details"/>
The page opens, but the datacontext is not available. I get the following error:
System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
Has anyone an idea how I could open the details view and providing the datacontext? I cannot move the DetailsViewModel to another class, because it is only possible to update it there.
Thanks
I have solved the problem by creating a helper to go up the visual tree and use the datacontext of the element i needed. Thanks for everybody who tried to help :)
the method looks like this:
public static UIELEMENT FindUiElementUpVisualTree(DependencyObject initial)
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(UIELEMENT))
{
current = VisualTreeHelper.GetParent(current);
}
return current as UIELEMENT;
}
where UIELEMENT is the object you are looking for e.g. a window or a button or something.
usually views and viewmodels have a one to one relationship. In this case it seems like there is a many to one relationship. How about a DetailsPageViewModel?

Categories

Resources