Sharing non control related data between WPF ViewModel and UserControl - c#

I'm new to WPF and the MVVM pattern so keep that in mind.
The project I'm tasked with working on has a view and a view model. The view also contains a user control that does NOT have a view model. There is data (custom object ... Order) being passed to the view model that I also need to share with the user control.
It looks like the UserControl does share data between the view model already via DependencyPropertys but this data is just text boxes on the user control that look to be bound back to propertys on the view model.
I need to share data that will NOT be represented by a control on the user control. Is there a good way to pass this data (complex Order object)? Maybe I do need some kind of hidden control on my user control to accomplish this but I'm just not that sure being new to this. Any advice would be appreciated.

There is no need for hidden fields (or any such concept in WPF) as you can add any custom properties you want to a user control.
In the user control, create a new dependancy property like this but with MyUserControl set apropriately:
public Order CurrentOrder
{
get { return (Order)GetValue(CurrentOrderProperty); }
set { SetValue(CurrentOrderProperty, value); }
}
// Using a DependencyProperty as the backing store for CurrentOrder. This enables binding, etc.
public static readonly DependencyProperty CurrentOrderProperty =
DependencyProperty.Register("CurrentOrder", typeof(Order), typeof(MyUserControl), new PropertyMetadata(null, OnCurrentOrderPropertyChanged));
public static void OnCurrentOrderPropertyChanged(DependencyObject Sender, DependencyPropertyChangedEventArgs e)
{
var sender = Sender as MyUserControl;
var NewValue = e.NewValue as Order;
var OldValue = e.OldValue as Order;
if (OldValue != null && sender != null)
{
//Use old value as needed and use sender instead of this as method is static.
}
if (NewValue != null && sender != null)
{
//Use new value as needed and use sender instead of this as method is static.
}
}
In you're parent view where you use the usercontrol you then write something like:
<local:MyUserControl CurrentOrder="{Binding ViewModelOrder}" />
Where CurrentOrder is the dependancy property on the usercontrol and ViewModelOrder is the name of the property in the view model you would need to replace local:MyUserControl with your control name/namespace.

You can simply create a dependency property in the class of your UserControl and bind to it in the View that uses the control. There is no need to internally bind the dependency property to one of the controls in the UserControl.

Related

PropertyGrid - Specify ExpandableObject if I don't have control of the class

I try to use a PropertyGrid (actually, it's the xceed wpf toolkit propertygrid, but I can switch to the standard forms PropertyGrid if that makes it easier), and the object I need to show in the grid has some child-objects that I need to be able to expand.
I found out I can achieve this by marking the properties with the "ExpandableObject" attribute. However, in some cases I am not the author of the class (or I am, but don't want to clutter it with GUI-stuff), so I cannot add attributes like that.
Is there any other way to tell the PropertyGrid which properties that should be expandable?
Xceed property grid has an event PreparePropertyItem. You can handle it and set e.PropertyItem.IsExpandable property. There is an example of handler that makes all non primitive properties expandable:
private void propertyGrid_PreparePropertyItem(object sender, Xceed.Wpf.Toolkit.PropertyGrid.PropertyItemEventArgs e)
{
var item = e.Item as Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem;
if (item == null)
return;
if (!item.PropertyType.IsValueType && item.PropertyType != typeof(string))
{
e.PropertyItem.IsExpandable = true;
}
}

Passing data between user controls in wpf

I have a user control with a button which when clicked opens a new user control.
private void Button_Click(object sender, RoutedEventArgs e)
{
Window window = new Window
{
Title = "Window2",
Content = new UserDataControl2()
};
window.ShowDialog();
}
I need to pass a collection to the new user control. How can I do it?
The easiest way is to create a custom constructor for your user control.
// Button_Click event
Window window = new Window
{
Title = "Window2",
Content = new UserDataControl2("My Data");
};
// User Control class.
string _info;
public UserDataControl2(string info)
{
_info = info.
};
You could also create a method or property in the user control to receive the data as well. Use whichever seems more appropriate in your context.
The best way is passing object to DataContext of this Window. For this you will need to create a class where store this parameters (ViewModels) and after "binding" to the Window (View). After you can pass this object assigning to Datacontext.
Look to MVVM model to understand better what I mean.
MVVM Pattern Made Simple
MVVM in Depth

Retrieve the DataContext of the Content set by using ContentSource property

I need to get the DataContext of the View set by using ContentSource property of the ModernWindow, Could you please help.I am using MVVM framework with Modern UI. The ViewModel code from where I need to show another window is as follows,
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Owner = Application.Current.MainWindow;
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
// Here I need to get the DataContext of PromptWindow's Content
this.PromptWindow.Show();
}
I did some debugging and found that by inherting IContent interface from ModernUI in the 'OnNavigatedTo' event
public void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
{
IPWPMainViewModel pwpMainViewModel = ObjectFactory.GetInstance<IPWPMainViewModel>();
pwpMainViewModel.PromptMainsCollection.Add(new ContentControl { Content = e.Content });
IPromptMainViewModel promptMainViewModel = ((UserControl)e.Content).DataContext as IPromptMainViewModel;
}
Here I am able to get the DataContext of the ModernWindow's Content i.e. of type 'IPromptMainViewModel' but here its very difficult to map/load the views into this ModernWindow as there are multiple instances of views, but I would like to do it in the ViewModel where 'ShowPrompt()' is present as there the Model will be associated with the View correctly so I can map there the views easily.
Thank you.
To get this done, I set the Content of the ModernWindow by myself (as shown in below code in a method in a ViewModel) without using the ContentSource DependencyProperty, If we use the ContentSource property it will be set for a ModernFrame type by the ModernWindow itself creating its Content instance after Navigation to that View completes in some method in ModernFrame class from ModernUI for WPF by using ModernFrame's Source DependencyProperty.
public void ShowPrompt()
{
this.PromptWindow = ObjectFactory.GetInstance<IPromptWindowViewModel>().Window as ModernWindow;
this.PromptWindow.Title = string.Concat("Control ", this.PromptOriginsEntity.PromptOriginsIdentity);
this.PromptWindow.Tag = this.PromptOriginsEntity.PromptOriginsIdentity;
this.PromptWindow.Owner = Application.Current.MainWindow;
// Store Window object in PromptWindowsCollection
this.PWPMainViewModel.PromptWindowsCollection.Add(this.PromptWindow);
this.PromptWindow.Show(); // inorder to retrieve the ModernFrame the ModernWindow is to be shown first
ModernFrame frameContent = (ModernFrame)this.PromptWindow.Template.FindName("ContentFrame", this.PromptWindow);
UserControl userControl = new UserControl { Content = GetView<IPromptViewModel>(), Tag = this.PromptOriginsEntity.PromptOriginsIdentity };
frameContent.Content = userControl;
this.PWPMainViewModel.PromptsCollection.Add(userControl);
IPromptViewModel promptViewModel = (IPromptViewModel)((IView)userControl.Content).DataContext;
promptViewModel.PromptEntity.Identity = this.PromptOriginsEntity.PromptOriginsIdentity;
}
I've uploaded a prototype app at https://wpfmvvmsamples.codeplex.com/SourceControl/latest
Thanks.

How to hide part of a WPF control conditionally?

I have a control in an assembly that I can't change that is very similar to the .NET DateTimePicker. I want to hide the time picker portion of that control when a certain condition is met (Property value on my ViewModel). The control looks like this:
[TemplatePart(Name = "PART_DatePicker", Type = typeof (DatePicker))]
[TemplatePart(Name = "PART_TimePicker", Type = typeof (TimePicker))]
public class MyDateTimePicker : Control {/*...*/}
This answer shows a nice way to always hide a PART of a control, but I want to do it dynamically:
How to hide a part of a WPF control
I imagine there are a few ways to do this. What I want is something minimal (like in the linked question's answer) as well as something that doesn't violate MVVM. System.Interactivity behaviors and triggers are fair game.
Create a new control extending the previous one
public sealed class MySuperiorDateTimePicker : MyDateTimePicker
{
//....
Add a DependencyProperty that you can bind to your ViewModel's state
public static readonly DependencyProperty HideItProperty =
DependencyProperty.Register(
"HideIt",
typeof(bool),
typeof(MySuperiorDateTimePicker ),
new UIPropertyMetadata(false, HideItPropertyChanged));
//snip property impl
Wait for the property to change, then hide your UI
private static void HideItPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
(d as MySuperiorDateTimePicker).OnHideItChanged((bool)e.OldValue,
(bool)e.NewValue);
}
private void OnHideItChanged(bool oldValue, bool newValue)
{
if(BusyTemplate == null)
return;
FindTimePicker().Visibility = newValue ? Visibility.Visible :
Visibility.Collapsed;
}
private UIElement FindTimePicker()
{
//snip null checks
return GetTemplateChild("PART_TimePicker") as UIElement;
}
Be careful with FindTimePicker as your DP might change before the control is loaded, and GetTemplateChild will return null. The usual thing to do is, in OnHideItChanged, if GetTemplateChild returns null use Dispatcher.BeginInvoke to re-run the event handler later on (ApplicationIdle or earlier).
When you find yourself saying "How can I do UI work using MVVM" stop and rethink your true goals. MVVM != no codebehind, no custom controls, etc.
One solution would be to hide it with the help of a DataTrigger defined in the datatemplate, so that when a certain value in the datacontext of the control is set to true/false then you will hide/show the part.
A quick search and i found some links that you might find useful:
http://zamjad.wordpress.com/2010/06/22/conditionally-hide-controls-from-data-template/
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/ae2dbfb7-5dd6-4352-bfa1-53634289329d/
The solution that worked for me was to edit the style of the control. Using Blend, I edited a copy of the style of the DateTimePicker, and added a binding to Visibility of the TimePicker that looks at my VM and converts the value of the enumeration.

How to update a custom dependency property when the datasource list changes

We have a user control with a custom dependency property (DP). The DP is bound to an ObservableCollection.
When a new item is added to the collection programatically, the databinding does not update the target DP. Why? We think it's because, unfortunately, in our case the target is not a ListBox or ListView, but a Canvas. The DP, when changed or initialized, is supposed to draw a Shape (!) onto the Canvas, and the shape's position and size is bound to the collection item's two properties: WIDTH, LEFT.
Ideally we don't want to clear the Canvas and redraw all items just becasue one has been added (or deleted). But how?
So:
How can the custom DP take care of drawing the shape for the new collection item? What callback do we need, at what point in time does this have to happen, and what specific MetaDataOptions might there?
Also, are there any good resources out there concerning all these dependency property options. They are quite confusing. MSDN does not really help with what we're trying to do.
Thanks!
EDIT:
The ObservableCollection is like so:
public class Projects : ObservableCollection<Project>
{
//no ommitted code. this class really IS empty!
}
The DP is like so:
public class MyUserControl : UserContorl
{
public static readonly DependencyProperty... etc. typeof(Projects)
private static void OnProjectsChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MyUserControl u = d as MyUserControl;
CpProjectCollection projects = e.NewValue as CpProjectCollection;
u.Refresh(projects);
}
private void Refresh(CpProjectCollection projects)
{
foreach (CpProject p in projects)
{
//...add each project to the Canvas
ProjectContorl pc = new ProjectControl();
pc.Project = project;
Binding b = new Binding("StartTime");
b.Converter = new TimeSpanConverter();
b.Source = pc.Project;
b.Mode = BindingMode.TwoWay;
c.SetBinding(Canvas.LeftProperty, b);
//do the same for project duration
}
}
}
If you bind to ObservableCollection, you get the change notification if the collection is replaced with another collection, not when the collection's content is changed. So, you'll need to subscribe to CollectionChanged event in your code-behind.
If you subscribe to CollectionChanged, you can see which are the new/deleted items in your ObservableCollection. You can add a new shape for each new item and remove old shapes for deleted items.

Categories