Can a WPF user control be written in F#?
Lets say I have a standard WPF/C# user control as:
public class DataGridAnnotationControl : UserControl
{
static DataGridAnnotationControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridAnnotationControl), new FrameworkPropertyMetadata(typeof(DataGridAnnotationControl)));
}
public DataGridAnnotationControl()
{
BorderBrush = Brushes.Black;
Background = Brushes.AliceBlue;
BorderThickness = new Thickness(20, 20, 20, 20);
}
public string LastName
{
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register("LastName", typeof(string), typeof(DataGridAnnotationControl), new PropertyMetadata(string.Empty));
}
How is this coded in F#?
TIA
In general, creating the user control in F# (without a library) will typically be quite different than in C#.
The main issue is you can't use partial classes, so the designer will not function. Even if you ditch the designer, the typical workflow with XAML files does not work properly. To do this in "pure" F#, you typically need to write the UI in code vs doing it in XAML and allowing the generated InitializeComponent() method to wire things together.
However, one way to get there in a more "natural" method is to use FsXaml. It allows you to write user controls directly which become usable in a similar way to C# developed ones. This is done via a type provider and overriding the default information.
Related
We have a (massive) legacy WinForms app which, through a menu item, opens up a WPF form. This WPF form will host an Infragistics grid, and some buttons/drop-downs.
This lone WPF form represents the nascent stage of a migration to WPF. Later on, more components of the app will move to WPF, and ultimately the entire app itself.
As part of the migration, we would like to use Caliburn Micro. Hence, it would be nice if we could start by using it with this lone WPF form.
Can someone please provide some pointers on how to use Caliburn Micro with the WPF form?
Or perhaps tell me why it may not make sense to use Caliburn Micro just yet?
The documentation I've read so far involves boot strappers that ensure the application starts with the desired root view model, rather than the scenario above.
Many thanks!
After much Googling and going through the Caliburn Micro source code, I've come up with an approach that works in a sample test application. I can't post the test application here for certain reasons, but here's the approach in a nutshell.
Create a WinForm with a button.
On button click, show a ChildWinForm
In the load handler of the ChildWinForm:
// You'll need to reference WindowsFormsIntegration for the ElementHost class
// ElementHost acts as the "intermediary" between WinForms and WPF once its Child
// property is set to the WPF control. This is done in the Bootstrapper below.
var elementHost = new ElementHost{Dock = DockStyle.Fill};
Controls.Add(elementHost);
new WpfControlViewBootstrapper(elementHost);
The bootstrapper above is something you'll have to write.
For more information about all it needs to do, see Customizing the Bootstrapper from the Caliburn Micro documentation.
For the purposes of this post, make it derive from the Caliburn Bootstrapper class.
It should do the following in its constructor:
// Since this is a WinForms app with some WPF controls, there is no Application.
// Supplying false in the base prevents Caliburn Micro from looking
// for the Application and hooking up to Application.Startup
protected WinFormsBootstrapper(ElementHost elementHost) : base(false)
{
// container is your preferred DI container
var rootViewModel = container.Resolve();
// ViewLocator is a Caliburn class for mapping views to view models
var rootView = ViewLocator.LocateForModel(rootViewModel, null, null);
// Set elementHost child as mentioned earlier
elementHost.Child = rootView;
}
Last thing to note is that you'll have to set the cal:Bind.Model dependency property in the XAML of WpfControlView.
cal:Bind.Model="WpfControls.ViewModels.WpfControl1ViewModel"
The value of the dependency property is used passed as a string to Bootstrapper.GetInstance(Type serviceType, string key), which must then use it to resolve the WpfControlViewModel.
Since the container I use (Autofac), doesn't support string-only resolution, I chose to set the property to the fully qualified name of the view model. This name can then be converted to the type, and used to resolve from the container.
Following up on the accepted answer (good one!), I'd like to show you how to implement the WinForms Bootstrapper in a ViewModel First approach, in a way that:
You won't have to create a WPF Window and,
You won't have to bind directly to a ViewModel from within a View.
For this we need to create our own version of WindowManager, make sure we do not call the Show method on the Window (if applicable to your case), and allow for the binding to occur.
Here is the full code:
public class WinformsCaliburnBootstrapper<TViewModel> : BootstrapperBase where TViewModel : class
{
private UserControl rootView;
public WinformsCaliburnBootstrapper(ElementHost host)
: base(false)
{
this.rootView = new UserControl();
rootView.Loaded += rootView_Loaded;
host.Child = this.rootView;
Start();
}
void rootView_Loaded(object sender, RoutedEventArgs e)
{
DisplayRootViewFor<TViewModel>();
}
protected override object GetInstance(Type service, string key)
{
if (service == typeof(IWindowManager))
{
service = typeof(UserControlWindowManager<TViewModel>);
return new UserControlWindowManager<TViewModel>(rootView);
}
return Activator.CreateInstance(service);
}
private class UserControlWindowManager<TViewModel> : WindowManager where TViewModel : class
{
UserControl rootView;
public UserControlWindowManager(UserControl rootView)
{
this.rootView = rootView;
}
protected override Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings)
{
if (isDialog) //allow normal behavior for dialog windows.
return base.CreateWindow(rootModel, isDialog, context, settings);
rootView.Content = ViewLocator.LocateForModel(rootModel, null, context);
rootView.SetValue(View.IsGeneratedProperty, true);
ViewModelBinder.Bind(rootModel, rootView, context);
return null;
}
public override void ShowWindow(object rootModel, object context = null, IDictionary<string, object> settings = null)
{
CreateWindow(rootModel, false, context, settings); //.Show(); omitted on purpose
}
}
}
I hope this helps someone with the same needs. It sure saved me.
Here are somethings you can start with
Create ViewModels and inherit them from PropertyChangedBase class provided by CM framework.
If required use the EventAggregator impelmentation for loosly coupled communication \ integration
Implement AppBootStrapper without the generic implementation which defines the root view model.
Now you can use the view first approach and bind the view to model using the Bind.Model attached property on view. I have created a sample application to describe the approach here.
I just thought about converting one of our custom controls to WPF, however it uses another custom control of ours that is written Winforms.
As there is little point in using WPF without MVVM. How can I databind to a Winforms control that is used within a WPF control.
(I don’t have much WPF experience, so I may be missing the point completely)
I think you will have to wrap the WinForms control in a class exposing DependencyProperties or at least implementing INotifyPropertyChanged.
So you'd have a class such as:
public class WinFormsWrapper : WindowsFormsHost
{
//You'll have to setup the control as needed
private static MyWinFormsControl _control;
public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(WinFormsWrapper),
new FrameworkPropertyMetadata(_control.IsSpinning, new PropertyChangedCallback(IsSpinning_Changed)));
private static void IsSpinning_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
_control.IsSpinning = (bool)e.NewValue;
}
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
}
Assuming that you have an IsSpinning property on your WinForms control. Depending on your needs, it may be simpler to implement INotifyPropertyChanged instead of using DependencyProperties.
Added by Ian Ringrose:
This example code is clearly wrong (see comments about _control being static), but shows how to solve the problem. As I am not using WPF at present, I am not going to edit the code as I can't test my edits.
I am keeping this answer as the accepted answer, as it contains the information needed to solve the problem.
I have added this to the answer, as comments do sometimes get deleted and this is getter views from people doing google searches.
I have created window derived class (WindowAttachedCollection.MyWindow) and attached property which holds collection of these windows. But WPF designer in VS 2010 tries to create WindowInstance object for each window in that collection and it throws ArgumentException:
The value "Microsoft.Expression.Platform.WPF.InstanceBuilders.WindowInstance" is not of type "WindowAttachedCollection.MyWindow" and cannot be used in this generic collection.
Parameter name: value
So it breaks WPF designer.
Is there any way how to disable instancing WindowInstance instead of MyWindow in WPF designer? At this time I don't require any design-time support for this collection of MyWindow.
EDIT:
public static readonly DependencyPropertyKey DialogsPropertyKey = DependencyProperty.RegisterAttachedReadOnly(
"DialogsInternal",
typeof(ObservableCollection<MyWindow>),
typeof(MyWindow),
new PropertyMetadata(null));
public static readonly DependencyProperty DialogsProperty = DialogsPropertyKey.DependencyProperty;
public static void SetDialogs(UIElement element, ObservableCollection<MyWindow> value)
{
element.SetValue(DialogsPropertyKey, value);
}
public static ObservableCollection<MyWindow> GetDialogs(UIElement element)
{
var dialogs = (ObservableCollection<MyWindow>)element.GetValue(DialogsProperty);
if (dialogs == null)
{
dialogs = new ObservableCollection<MyWindow>();
SetDialogs(element, dialogs);
}
return dialogs;
}
Since your code will actually be executed at design time, you can simply have it conditionally do something that will make the designer not do anything unpleasant, to the extent that that is possible. To accomplish this you need to be able to detect programmatically that you are running under the designer and you can use DesignerProperties.IsInDesignModeProperty for that as described here:
Detecting design time mode in WPF and Silverlight
I decided to change base class of MyWindow from Window to ContentControl. For our purposes it is sufficient. Each ContentControl is wrapped into a Window when becomes active.
I have a WinForm UserControl inside a WPF window and the WPF code is using the MVVM pattern.
What is the best way to successfully integrate the WinForm control into the MVVM pattern?
Can I use some form of binding from the WPF side?
Let's say that I want to handle some events from the WF control, is there a way to fully go MVVM?
Thanks.
Note that this doesn't really answer the questions (I should have read better). If you're interested in using a WPF control in a WinForms app, here's an approach. My scenario is:
1) Have a WinForms control that is used many places in my app.
2) Want to develop a WPF implementation that will use the MVVM pattern.
3) Want to write the control as a proper WPF control complete with dependency properties so it can be used properly when my app is eventually all WPF.
4) Want to keep the same WinForms control and API to not break existing client code in my app.
Most everything was straightforward except for having my WinForms control raise events when properties of my WPF control changed. I wanted to use a binding but since the source of a binding must be a DependencyObject and a System.Windows.Forms.UserControl is not, I had to make a simple nested class. I wrote my WPF control exactly as if I was integrating it into a WPF application, and just did some extra thunking to get my WinForms wrapper to work.
Here's code for my WPF control:
public partial class MonkeySelector : UserControl
{
public static readonly DependencyProperty SelectedMonkeyProperty =
DependencyProperty.Register(
"SelectedMonkey", typeof(IMonkey),
typeof(MonkeySelector));
public MonkeySelector()
{
InitializeComponent();
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// Note: No code is shown for binding the SelectedMonkey dependency property
// with the ViewModel's SelectedMonkey property. This is done by creating
// a Binding object with a source of ViewModel (Path = SelectedMonkey) and
// target of the SelectedMonkey dependency property. In my case, my
// ViewModel was a resource declared in XAML and accessed using the
// FindResource method.
}
public IMonkey SelectedMonkey
{
get { return (IMonkey)GetValue(SelectedMonkeyProperty); }
set { SetValue(SelectedMonkeyProperty, value); }
}
}
Here's the code for my WinForms control:
public partial class WinFormsMonkeySelector : UserControl
{
public event EventHandler SelectedMonkeyChanged;
private MonkeySelector _monkeySelector;
private WpfThunker _thunker;
public WinFormsMonkeySelector()
{
InitializeComponent();
_monkeySelector = new MonkeySelector();
_elementHost.Child = _monkeySelector;
System.Windows.Data.Binding binding = new System.Windows.Data.Binding("SelectedMonkey");
binding.Source = _monkeySelector;
binding.Mode = System.Windows.Data.BindingMode.OneWay;
_thunker = new WpfThunker(this);
// Note: The second parameter here is arbitray since we do not actually
// use it in the thunker. It cannot be null though. We could declare
// a DP in the thunker and bind to that, but that isn't buying us anything.
System.Windows.Data.BindingOperations.SetBinding(
_thunker,
MonkeySelector.SelectedMonkeyProperty,
binding);
}
protected virtual void OnSelectedMonkeyChanged()
{
if (SelectedMonkeyChanged != null)
SelectedMonkeyChanged(this, EventArgs.Empty);
}
public IMonkey SelectedMonkey
{
get { return _monkeySelector.SelectedMonkey; }
set { _monkeySelector.SelectedMonkey = value; }
}
private class WpfThunker : System.Windows.DependencyObject
{
private WinFormsMonkeySelector _parent;
public WpfThunker(WinFormsMonkeySelector parent)
{
_parent = parent;
}
protected override void OnPropertyChanged(System.Windows.DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
// Only need to check the property here if we are binding to multiple
// properties.
if (e.Property == MonkeySelector.SelectedMonkeyProperty)
_parent.OnSelectedMonkeyChanged();
}
}
}
Personally, I would handle this by creating a WPF UserControl that wraps the Windows Forms control. This would allow you to encapsulate all of the required code-behind into your WPF Control, and then use it in a pure MVVM manner.
It will be difficult to stay "pure" MVVM using a Windows Forms control directly, as Windows Forms controls typically require a different binding model, as well as typically requiring direct event handling.
You might have a look at the WAF Windows Forms Adapter. It shows a possible way to use Windows Forms together with MVVM.
Is it possible to introduce 'custom' attributes into different UI Elements in XAML ? Also to read them later like we add attributes for server controls in ASP.NET ?
I intend to read specific attributes and operate on them together.
It sounds like you're trying to find Attached Properties.
An attached property lets you add in a property, definable in Xaml, which can be "attached" to any UIelement. You then retrieve them in code like any other Dependency Property.
Here is the approach I tend to take with this.
Create a new class file called Meta:-
namespace SilverlightApplication1
{
public static class Meta
{
#region SomeValue
public static string GetSomeValue(DependencyObject obj)
{
return (string)obj.GetValue(SomeValueProperty);
}
public static void SetSomeValue(DependencyObject obj, string value)
{
obj.SetValue(SomeValueProperty, value);
}
public static readonly DependencyProperty SomeValueProperty =
DependencyProperty.RegisterAttached("SomeValue", typeof(string), typeof(Meta),
new PropertyMetadata(null));
#end region
#region SomeOtherValue
// Boilerplate code from above.
#end region
}
}
A value can now be attached in XAML like this:-
<TextBox x:Name="txt" local:Meta.SomeValue="Hello, World!" />
At some point in code this value can be retrieved with:-
string value = Meta.GetSomeValue(txt);
Note you don't have to stick with String as the type of the property you can pretty much use any type you like with the limitation that if you can to attach it in XAML the type must be compatible with the way XAML constructs objects (for example requires a default constructor).
The way I've accomplished that is by creating a new class that inherits the base control.
For example, I have a class called WebTextBox that inherits TextBox. And inside WebTextBox are some custom properties and events. By doing this you're inheriting all the behaviors of the TextBox control. But you can get creative here if you choose, even modifying the behavior by overriding events and such.
Anyway, after you create the class you'll then have to add the namespace for the project to the XAML. Something like this:
xmlns:me="clr-namespace:YourNamespace;assembly=YourAssembly"
And then you can add a WebTextBox (or whatever you call it) like this:
<me:WebTextBox CustomAttribute="cool stuff" />