Silverlight: Dependency property loses value when control is navigated away from - silverlight

I have a custom control that has two dependency properties. They are set as such:
Public Shared ReadOnly ValueBindingProperty As DependencyProperty = _
DependencyProperty.Register("ValueBinding", GetType(String), GetType(HomePageControl), New PropertyMetadata(String.Empty))
Public Property ValueBinding As String
Get
Return DirectCast(Me.GetValue(HomePageControl.ValueBindingProperty), String)
End Get
Set(value As String)
Me.SetValue(HomePageControl.ValueBindingProperty, value)
End Set
End Property
I assess them in the PropertyChangedCallback of another DP like this:
Dim hpc As HomePageControl = DirectCast(d, HomePageControl)
Dim valueBindingString as String = hpc.ValueBinding
And then I use it where I need it.
The value is there the first time that the page is loaded and the control is loaded. Once I navigate away from the page - using Silverlight navigation loading a new "view" into the frame - the value disappears and when I go back to the page/view that the control is on the value equals the default value instead of what it is set to in the xaml. What happened to the value?
I have other DP that are strings that remain set. As far as I can tell these two are set the same as the others. Why would these lose their value when the others don't?

When navigation occurs, Silverlight removes the old page from the tree and adds a new one which has just been created. Therefore, when you navigate to a URI and come back, you get a new control which is in its default state.
You should store the state of the controls somewhere aside from them. It may be an in-memory object or a server-side database table. The choice depends on your requirements. Probably, the best and a universal approach is applying the MVVM pattern.

Related

Working with ObservableCollections in WPF and MVVM

I'm fairly new to WPF and still try to get the feeling on how to do something with built-in functions rather than inventing the wheel on my own again.
Today I stumbled upon a problem, that I couldn't solve with built-in functions and the possible ways I could think of I didn't like very much. So hopefully you can point me in the right direction or even can name a clever way with built-in functions.
So, for the sake of simplicity let's say I'd like to write a ViewModel for the MailMessage class that can be found in the System.Net.Mail namespace.
Imports System.Collections.ObjectModel
Imports System.Net.Mail
Public Class MailMessageViewModel
Private _message As MailMessage
...
End Class
A MailMessage object has (among others) a property To of type MailAddressCollection containing all the recipients for my e-mail as MailAddress objects.
In my ViewModel I wrap this collection of MailAddress objects into an ObservableCollection.
And here's my first question, how do I do that. Do I use:
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
Return New ObservableCollection(Of MailAddress)(_message.To)
End Get
End Property
or do I use:
Private _recipients As ObservableCollection(Of MailAddress)
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
If _recipients Is Nothing Then
_recipients = New ObservableCollection(Of MailAddress)(_message.To)
End If
Return _recipients
End Get
End Property
My view model now has a bindable property Recipients.
Now I'd like to be able to delete an e-mail address from the To collection of my MailMessage.
But when I delete an address from the ObservableCollection, my UI gets updated properly, but the To collection stays untouched. If I delete directly from the To collection of my MailMessage, the ObservableCollection and therefore my UI don't reflect the changes.
Do I really have to wire the ObservableCollection and the corresponding source collection manually by using the CollectionChanged event or by doing all changes twice (in the ObservableCollection and in the real collection)? Or is there any clever WPF way I don't know of?
Don't "wrap" anything.
Simply create a View Model containing properties needed to send your mail message.
At some point in future, you'll actually be sending the message. For example, the user clicks a Send button that fires an ICommand somewhere. At this time, convert your ViewModel into a MailMessage.
You cannot "wrap" one collection within another without lots of code. It only takes a few minutes to copy property values from an instance of one type to an instance of another type.
If the changes always go from the ObservableCollection to the original List, i think that you could add a handler to 'CollectionChanged' event of the ObservableCollection. I think that doing it this way won't be so onerous.
AddHandler Recipients.CollectionChanged, AddressOf RecipientsCollChanged
....
Private Sub RecipientsCollChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.OldItems IsNot Nothing Then
For Each elem In e.OldItems
_message.To.Remove(elem)
Next
End If
End Sub
Obviously, if you want, you can also handle the modify and the adding of elements into the ObservableCollection using the informations into the NotifyCollectionChangedEventArgs parameter.

Entity Framework confusions

This is WPF + MVVM + EF. I have a table of Sales Orders in my DB. In the UI, I show a ComboBox with all SalesOrderNumbers and a Grid with Labels and TextBoxes that shows the details of selected order in the ComboBox. Consider the following ViewModel:
Class SalesOrderViewModel
Public Property AllSalesOrderNumbers As List(Of Integer)
Public Sub New()
AllSalesOrderNumbers = context.SalesOrders.Select(Function(x) x.orderNumber).ToList()
If AllSalesOrderNumbers.Count > 0 Then SelectedOrderNumber = AllSalesOrderNumbers(0)
End Sub
Private Property mSelectedOrderNumber As Integer
Public Property SelectedOrderNumber As Nullable(Of Integer)
Get
Return mSelectedOrderNumber
End Get
Set(value As Nullable(Of Integer))
mSelectedOrderNumber = value
End Set
End Property
Public ReadOnly Property SelectedOrder As SalesOrder
Get
Return context.SalesOrders.FirstOrDefault(Function(x) x.orderNumber = SelectedOrderNumber)
End Get
End Property
End Class
In the UI, ComboBox's ItemsSource is bound to AllSalesOrderNumbers and SelectedValue is bound to SelectedOrderNumber (with Mode=OneWayToSource). On the other hand, the Grid's DataContext is bound to SelectedOrder. Whole of it is working fine.
My question is about the New Sales Order button. For adding new record, have added an ICommand to my ViewModel that basically does the following:
Dim NewOrder = context.SalesOrders.CreateObject()
context.SalesOrders.AddObject(NewOrder)
mSelectedOrderNumber = NewOrder.orderNumber
AllSalesOrderNumbers.Add(mSelectedOrderNumber)
I'm confused about the following:
SelectedOrder queries the model for the current value of SelectedOrderNumber property. Since the DB doesn't yet have this new record, it returns null. How do I ask it to look into local context object before going to the DB?
Unlike DataSets, it doesn't assign a negatively incrementing identity value to orderNumber, so I'm wondering what will happen if I add another order.
I do not see the newly added order object in context.SalesOrders collection after the 2nd line above (context.SalesOrders.AddObject(NewOrder)) executes.
The technical answers:
1.SelectedOrder queries the model for the current value of SelectedOrderNumber property. Since the DB doesn't yet have this new record, it returns null. How do I ask it to look into local context object before going to the DB?
You can query the local collection of a DbSet like so:
context.SalesOrders.Local.FirstOrDefault(...)
2.Unlike DataSets, it doesn't assign a negatively incrementing identity value to orderNumber, so I'm wondering what will happen if I add another order.
When you add new Orders, EF will keep track of each of them as a separate instances.
3.I do not see the newly added order object in context.SalesOrders collection after the 2nd line above (context.SalesOrders.AddObject(NewOrder)) executes.
See 1. It's in the Local collection.
Architectural answer: having a context in your view model is not the best choice. A view model should not worry about its data, it should receive data and after that be self contained. Its responsibility is to respond to user interaction and notify these to the controller or service layer. So the model should be populated with a list of Orders, after which the context is disposed. When new orders are added the model should tell the controller, either instantly or after a save command, and maybe receive a command to (re)display the Order. As such the view model is relieved from knowing the technical details of a data layer.

Bound Value gets reset to the empty string

For my WPF application, I created several UserControls which each have their own ViewModel. The ViewModels are all based on a PageViewModelBase which contains a variable "_context".
The UserControls are presented as pages in a Wizard Dialog, which has its own WizardViewModel. WizardViewModel has the variable _masterContext which is passed to the respective child ViewModels via their constructors. For example,
Child1ViewModel vm = new Child1ViewModel(_masterContext);
and the constructor of Child1ViewModel :
public Child1ViewModel(Context context) : base(context)
and the PageViewModelBase :
protected PageViewModelBase(Context context)
{
_context = context;
}
My intention is to have only 1 _masterContext, which can be accessed via each of the ChildViewModels. And each of the child views can bind to this and supply values to various fields in the master context.
However I am encountering the problem that a field that I bind to TextBox.Text gets reset to "" whenever I switch from childView1 to childView2. I'm not sure is this due to my MMI code, or there are more than 1 instance of _masterContext in the application, i.e. my method above is not doing as it should.
What could be causing this?
Managed to find the culprit resetting the value. I implemented a behavior to handle the TextChanged event of the TextBox. Somehow that used in conjunction with the Binding will cause the value to be reset. Once I took that away, the binding was working fine.
Due to my limited knowledgem I'm unable to explain why. But thanks all for your time.

DataContext, DataBinding and Element Binding in Silverlight

I'm having one hell of a time trying to get my databinding to work correctly. I have reason to believe that what I'm trying to accomplish can't be done, but we'll see what answers I get.
I've got a UserControl. This UserControl contains nothing more than a button. Now within the code behind, I've got a property name IsBookmarked. When IsBookmarked is set, code is run that animates the look of the button. The idea is that you click the button and it visually changes. We'll call this UserControl a Bookmark control.
Now I have another control, which we'll call the FormControl. My FormControl contains a child Bookmark control. I've tried to do databinding on my Bookmark control, but it's not working. Here's some code to help you out.
This is the XAML and Loaded event handler of my control. As you can see it contains a child element that is a custom control (bookmark). So once this control loads, it's DataContext is set to an new instance of an Employee object. Silverlight also sets the DataContext property of my child bookmark control to the same instance. I've verified this by debugging. If my parent has a valid DataContext set then why can't my child control (bookmark) property databind to it?
<UserControl ......>
<q:Bookmark x:Name="BookMarkControl1" IsBookmarked="{Binding IsSiteBookmarked}" />
</UserControl>
public void Control_Loaded(object sender, EventArgs e)
{
DataContext = new Employee { IsSiteBookmarked = True };
}
This is my custom control below. Obviously it contains more than this, but for readability I've trimmed it down to the property I'm trying to databind to.
//this is the bookmark control. I've included this control within another control, and I'm trying to databind to properties within my parents DataContext
public partial class Bookmark : UserControl
{
bool _IsBookmarked= false;
public bool IsBookmarked
{
get {return _IsBookmarked;}
set {
_IsBookmarked= value;
SwitchMode(value);
}
}
}
UPDATE
Got some javascript errors that I should mention. Firebug reports a AG_E_PARSER_BAD_PROPERTY_VALUE exception. It doesn't seem like my databinding is even working yet.
Make your IsBookmarked property on the Bookmark control a dependency property.
I presume Control_Loaded is a part of your FormControl, in which case I'm not sure you are using DataContext properly. Best double check that.
UPDATE: Yes, you are using the DataContext properly. AG_E_PARSER_BAD_PROPERTY_VALUE indicates you need to make the IsBookmarked property a dependency property, like so:
Public Property IsBookmarked() As Boolean
Get
Return Me.GetValue(IsBookmarkedProperty)
End Get
Set(ByVal value As Boolean)
Me.SetValue(IsBookmarkedProperty, value)
End Set
End Property
Public Shared ReadOnly IsBookmarkedProperty As DependencyProperty = DependencyProperty.Register("IsBookmarked", GetType(Boolean), GetType(Bookmark), New PropertyMetadata(New PropertyChangedCallback(AddressOf OnIsBookmarkedPropertyChanged)))
Private Shared Sub OnIsBookmarkedPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim cntrl As Bookmark = TryCast(d, Bookmark)
cntrl.SetIsBookmarked(e.NewValue)
End Sub
If you only need to store the value for later use, then you don't need to do anything in the OnIsBookmarkedPropertyChanged procedure, But I put some code there as an example anyway.
Good Luck!
I don't recall the exact order in which databinding is evaluated (and I'm too lazy to go look it up), but as I recall, it initially happens BEFORE the form's Loaded event fires, and without making the IsBookmarked property a dependency property, or at least using INotifyPropertyChanged, it may have trouble establishing the datacontext appropriately. I'd recommend either implementing INotifyPropertyChanged or making IsBookmarked a dependency property. DataBinding is tough enough to get right in the best of circumstances (see my long, bad-tempered rant about it here), and you'll just be making it more difficult on yourself if you aren't setting up your properties in the way that it expects.
The control exposes a IsSiteBookmarked property(which I believe should be a DependencyProperty) but the control is binding to a IsBookmarked which is not shown. Is this intentional? Have you checked your Visual Studio output window for binding errors?
Addition 1:
Since you have fixed the typo in your question and added that there is an error being reported.
Start by clearing up the AG_E_PARSER_BAD_PROPERTY_VALUE problem. Is there a line number and start position in the error message? Start looking there. One strategy is to start taking out XAML until there is no longer an error. This will narrow down the offending code.
Running in debug, mode check for binding errors in the output window.
You might want to also post the Employee class code, especially the IsSiteBookmarked property.
Typically when doing databinding to an object you will want to leverage the INotifyPropertyChanged interface and implement that so that the control can properly invalidate it's property value. Unless you use INotifyPropertyChanged with Mode=TwoWay then any code that changes your DataContext's IsSiteBookmarked will have no effect.

Programatically setting design-time properties in Windows.Forms control

Is there an easy way to programatically setting a property value on a control such that it will be persisted in the designer-generated code?
I imagine a piece of code in the control constructor or load event which is executed when i open the control in design mode, but sets a property such that it will be persisted the same way as if I changed the value manually through the properties grid.
Edit: Yes, this would be the same as editing the designer code manually, but I want to do it programatically.
Presuming I understand the question
You can databind that property to a setting, using the Visual studio Gui. Check the properties for that control, under the Data section for (Application Settings), (Property Bindings).
It depends on what kind of functionality you want. If you only need the properties to be set when you add the control to a form, then setting the properties in the control's constructor works perfectly. However, changes you make using the Properties panel will take precedence, and setting properties in the control's constructor won't necessarily affect existing instances of the control.
If you want to be able to change the properties for instances of the control in one place, assigning bindings in (application settings), (property bindings) works. Then you can modify all the bindings from the Settings.settings file. This still requires you to assign property bindings for each instance of the control, though.
Now for the finale. If you want to set properties in the control's class that affect all instances of the control, whether the instances are yet to be created or already exist, you have to get a little creative. I found a solution, but it may not be the best. My solution goes like this:
In the control's constructor, for each property you want to set, you:
Store the desired value in a private variable.
Assign the variable's value to the property.
Assign an event handler that assigns the variable's value to the property whenever the property is changed.
A downside is the amount of coding for each property. Also, you wouldn't be able to change the properties from the Properties pane.
Do you think about something like:
if (this.DesignMode)
{
// do somthing
}
If you put this into the constructor remember to call InitializeComponent() before.
What about:
Private Function GetPropertyByName(ByVal propName As String) _
As PropertyDescriptor
Dim prop As PropertyDescriptor
prop = TypeDescriptor.GetProperties(l_dWindow)(propName)
If prop Is Nothing Then
Throw New ArgumentException( _
"Matching ColorLabel property not found!", propName)
Else
Return prop
End If
End Function
Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
GetPropertyByName("AnyPublicProperty").SetValue(AnyControl, "AnyStringVALUE")
Me.DialogResult = DialogResult.OK
End Sub

Resources