WPF Creating a Command Library - wpf

New to WPF/XAML and looking to build a Command library for my application. Current version of my application is "flat" meaning all the code behind resides in my MainWindow.cs file. I've decided to separate things out into User Controls and all the UI part so far works great but....
I'm having trouble binding my commands to things now that I've done this. To make it simple I created a new WPF project, added my Menu control (Controls\Menu.xaml) and referenced this in my MainWindow.xaml. Now, I've gone ahead and added a CommandLibrary class and can't seem to get anything working. Here is my File -> New command code:
public static class MyAppCommands
{
private static RoutedUICommand _NewItem;
static MyAppCommands()
{
_NewItem = new RoutedUICommand("Create a new project", "NewItem", typeof(MyAppCommands));
}
public static RoutedUICommand NewItem
{
get { return _NewItem; }
}
private string filePath = null;
private bool dataChanged = false;
public static void NewItem_Executed(object sender, ExecutedRoutedEventArgs e)
{
if (dataChanged)
{
string sf = SaveFirst();
if (sf != "Cancel")
{
ClearState();
}
}
else
{
ClearState();
}
}
public static void NewItem_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static void BindCommandsToWindow(Window window)
{
window.CommandBindings.Add(new CommandBinding(NewItem, NewItem_Executed, NewItem_CanExecute));
}
Where do I put the below in my class as a lot of my command will be using them...?
private string filePath = null;
private bool dataChanged = false;
Many Thanks!

I think you could benefit from setting up some design patterns like MVC or MVVM.
Check out these frameworks:
Caliburn (my favorite)
Prism
You can also follow MVC or MVVM just using a couple small helper classes, check out ariticles around the web, like this one MVVM.
Check this if you want a light-weight framework: MVVM Foundation

Related

CaliburnMicro IEventAggregator won't work with ChildViews

and sorry if this is a somewhat easy question, but I'm still new to CaliburnMicro and EventAggregator is proving to be the worst thing to learn by a long mile. Anyway, let's go to it. I have an app with a main ShellView and a bunch of ChildViews that display various information, and I need certain parameters to be shared between the Views. It did not take very long to find I need to use EventAggregator, but with Net6 I could not make it work, no chance. I found this app someone else did in Net3.1 and CaliburnMicro where a new window is created, on this second window there is a TextBox and a send button. Whatever you type here gets sended to the Main window. I studied the code and replicated the app succesfully, also with the latest version of Caliburn and Net6.
BUT then I decided to modify the app and instead of having a new window, now I have exactly the same but with a ChildView inside the ShellView, and here is where nothing works.
1st on the Bootstrapper
public class Bootstrapper : BootstrapperBase
{
private SimpleContainer _container;
public Bootstrapper()
{
Initialize();
}
protected override void Configure()
{
_container = new SimpleContainer();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.PerRequest<ShellViewModel>();
}
protected override object GetInstance(Type service, string key)
{
var instance = _container.GetInstance(service, key);
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return _container.GetAllInstances(service);
}
protected override void BuildUp(object instance)
{
_container.BuildUp(instance);
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<ShellViewModel>();
}
As seen, I've created a SimpleContainer, then initialised it as a Singleton.
Also _container.Singleton<IWindowManager, WindowManager>(); is no longer needed as I am not opening a new window anymore, so that could be commented but as I had so many issues, I let it be just in case for this last try before posting this question.
2nd I've created a class for the message IHandle to manage.
public class EventMessage
{
public string Text { get; set; }
}
3rd I created and edited the ChildViewModel (still called SecondWindowViewModel as this experiment is directly derivated from the original worknig one) and ChildView (well SecondWindowView still). Note that SecondWindowView is a WPF User Control, not a Window.
class SecondWindowViewModel : Screen
{
private readonly IEventAggregator _eventAggregator;
private string _secondTextBox;
public string SecondTextBox
{
get { return _secondTextBox; }
set { _secondTextBox = value; NotifyOfPropertyChange(() => SecondTextBox); }
}
public SecondWindowViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator= eventAggregator;
}
public void SendBack()
{
EventMessage ToSend = new EventMessage();
ToSend.Text = SecondTextBox;
_eventAggregator.PublishOnUIThreadAsync(ToSend);
}
As seen, I have an IEventAggregator _eventAggregator, then on the constructor of the class I added this._eventAggregator= eventAggregator; and then on the method SendBack that is called upon pressing the button I send the message with SubscribeOnUIThread.
And lastly the ShellViewModel:
public class ShellViewModel : Conductor<Object>, IHandle<EventMessage>
{
private readonly IEventAggregator _eventAggregator;
private string _parentText;
public string ParentText
{
get { return _parentText; }
set { _parentText = value; NotifyOfPropertyChange(() => ParentText); }
}
public ShellViewModel(IEventAggregator eventAggregator)
{
ActivateItemAsync(new SecondWindowViewModel(_eventAggregator));
_eventAggregator = eventAggregator;
_eventAggregator.Subscribe(this);
}
public Task HandleAsync(EventMessage message, CancellationToken cancellationToken)
{
ParentText = message.Text;
return Task.CompletedTask;
}
//public void NewWindow()
//{
//WindowManager wm = new WindowManager();
//SecondWindowViewModel swm = new SecondWindowViewModel(_eventAggregator);
//wm.ShowWindowAsync(swm);
//}
}
}
Now here instead of inheriting from Screen and IScreen, I inherit from Conductor because I want to have that ChildView on my form. NewWindow is how it worked before but now that button no longer works as I don't need to launch a new window anymore, that's why it is commented out.
As seen, on the contructor I subscribe _eventAggregator, and then HandleAsync does the job of receiving the message and assign it to a variable. Now on the Caliburn Documentatin the method to use is public void Handle() but that no longer works, that's the only way I managed to make it work.
Now when I run this the app does load and seems to work just fine, but as soon as the SendBack() method gets called (in SecondWindowViewModel) the line _eventAggregator.PublishOnUIThreadAsync(ToSend); launches an exception System.NullReferenceException: 'Object reference not set to an instance of an object.'
From my understanding EventAggregator should not care if I'm sending the message to a Window or a user panel or anything. Only thnig I changed is commenting out NewWindow and deleting the old SecondWindowWiew WPF Window and replacing it with a new SecondWindowView WPF User Control with the exact same XAML, then in ShellWiev added bellow a <ContentControl x:Name="ActiveItem"/>.
I'm a bit of a loss here, I've been trying everything, coping the code from the documentation, looking for tutorials online, other StackOverflow questions, and while I could make the UI load and make HandleAsync work, for 3 days straight I could not make it work with ChildViews. That code I wrote does work with a new window. I even run into problems of ShellView straight up not loading unless putting a new constructor that takes no parameters and empty inside, but that's for another day.
Sorry for the extra long post, but I think it's important to put all the information out there. Thank you for your time and again, sorry if this is somewhat of a dumb question, but we all have to start somewhere no?

Using Prism for building a Plugin (.dll)

I am developing a plugin to be used for a software I use. The software plugin is attached to the software by attaching the .dll produced from my code. Therefore, the software's documentation requires you to have a certain class (call it CPlugin) as the plugins entry point.
All the tutorials I am reading with Prism is where you initiate your project as a WPF Application. This way your project will have App.xaml and App.cs files where you start to implement your Prism framework. Compiling the code also (for a WPF application) will produce a .exe not a .dll.
The way I have my plugin setup is I start my project with a C# class. I would then create my CPlugin class and initiate all my variables and then display my MainView which creates my ViewModel and take it from there. There is no App.xaml or App.cs. I am not sure how to use Prism with the constraints I have.
This is the software I am developing the plugin for: https://www.csiamerica.com/products/etabs
Upon installation in the install directory; the API helpfile can be found which explains how to develop or initiate a plugin. Here is a sample of the relevant information:
In your plugin, all functionality must be implemented in a class called cPlugin.
Class cPlugin must contain a subroutine cPlugin.Main that has two
reference arguments of types ETABSv1.cSapModel and
ETABSv1.cPluginCallback
Also
Adding a .NET Plugin
The process for adding a .NET plugin is much simpler. In the External Plugin Data form, the user should simply browse for and select the plugin .NET DLL and click the "Add" button
Here is some sample code for a plugin that displays an empty window:
Create a C# Class Library file (.NET Framework), reference the API as one of my references.
CPlugin.cs:
using Combonito.Views; // Access Views (open window)
using CSiAPIv1; //to Access ETABS/SAP2000 API
using System;
using static Globals;
namespace Combonito
{
// cPlugin has to be implemented in ETABS Plugins, it has to contain two functions Main() and Info()
public class cPlugin
{
private MainView _MyForm;
//Entry point of plugin - has to exist with this exact signature
// must call Finish() when plugin is closed!
public void Main(ref cSapModel _SapModel, ref cPluginCallback _ISapPlugin)
{
ParentPluginObject = this;
SapModel = _SapModel;
ISapPlugin = _ISapPlugin;
try
{
_MyForm = new MainView(); //Create MainView
_MyForm.ShowDialog(); // Display window
}
catch (Exception ex)
{
try
{
ISapPlugin.Finish(1);
Console.WriteLine(ex);
}
catch (Exception ex1)
{
Console.WriteLine(ex1);
throw;
}
}
}
// return value should be 0 if successful
public int Info(ref string txt)
{
try
{
txt = "Plugin is written by Moustafa El-sawy (mk.elsawy#live.com)";
}
catch (Exception ex)
{
Console.WriteLine(ex);
throw;
}
return 0;
}
//Deconstructor to clean up
~cPlugin()
{
Console.WriteLine(GC.GetGeneration(0));
}
}
}
I then have an empty window MainView and A viewmodel MainWindowViewModel.
Edit: Here is the boiler initial to start any plugin similar to what I showed above but has more explanation https://kinson.io/post/etabs-plugin-quickstart/
Your starting point should be Bootstrapper
First you need to install:
Prism.Unity
Prism.Wpf
Need to create your bootstrapper based on
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/PrismBootstrapperBase.cs
Override the virtual method to create Shell (which is your main view that contains regions
Override the virtual method to configure your container.
Finally register your views and viewmodels
P.S.: Consider using an interface for each registered type e.g. IShellViewModel
using Prism;
using Prism.Ioc;
using Prism.Unity;
using System.Windows;
namespace PrismTest
{
public class Bootstrapper : PrismBootstrapperBase
{
protected override IContainerExtension CreateContainerExtension()
{
return new UnityContainerExtension();
}
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IShellViewModel, ShellViewModel>();
}
}
}
Call your Bootstrapper from your plugin :
Bootstrapper bootstrapper = new Bootstrapper();
bootstrapper.Run();
Your View (Shell)
<Window
x:Class="PrismTest.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:PrismTest"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="Shell"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid>
<TextBox Text="{Binding MyText}" />
</Grid>
</Window>
In Shell.xaml.cs: Use your view model. This can be also automatically injected
public partial class Shell : Window
{
private readonly IUnityContainer _container;
public Shell(IUnityContainer container)
{
InitializeComponent();
_container = container;
this.DataContext = _container.Resolve<IShellViewModel>();
}
}
Your ViewModel
public class ShellViewModel : BindableBase, IShellViewModel
{
private string m_MyText = "Shell ViewModel Text";
public string MyText
{
get { return m_MyText; }
set { SetProperty(ref m_MyText, value); }
}
}
Your interface:
internal interface IShellViewModel
{
string MyText { get; set; }
}
Resulting view;

Using WPF Extended Toolkit MessageBox from other threads and curious behavior

I am having trouble using the WPF Extended Toolkit (version 2.1.0.0) MessageBox from other threads. The namespace is: Xceed.Wpf.Toolkit.MessageBox
I replaced my regular MessageBoxs (System.Windows.MessageBox) with the Toolkit MessageBox and get errors when I launch one from another thread. The System.Windows.MessageBox has no such problems. I saw this posting that reports the problem, but there seems to be no follow up:
https://wpftoolkit.codeplex.com/workitem/21046
I'm guessing there is a work around. An example is presented there that shows the problem, but here is my simple example:
First, I wrap the Toolkit.MessageBox. I do this primarily because I'm applying style (although I've commented that out to show that's not the problem)
public class CustomMessageBox
{
//static DummyUserControl1 _ctrl = new DummyUserControl1();
public static MessageBoxResult Show(string msgText, Style styleArg = null)
{
Cursor saveCursor = Mouse.OverrideCursor;
Mouse.OverrideCursor = null;
//Style style = styleArg != null ? styleArg : _ctrl.FindResource("MessageBoxStyle1") as Style;
// MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show(msgText, "", MessageBoxButton.OK, style);
MessageBoxResult result = Xceed.Wpf.Toolkit.MessageBox.Show(msgText, "", MessageBoxButton.OK);
Mouse.OverrideCursor = saveCursor;
return result;
}
}
The main window just has two buttons on it, and here's the code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnMainThreadMsgBox_Click(object sender, RoutedEventArgs e)
{
CustomMessageBox.Show("Hello on main thread");
}
private void btnAltThreadMsgBox_Click(object sender, RoutedEventArgs e)
{
Thread altThread1 = new Thread(new ThreadStart(AltThread1Proc));
altThread1.SetApartmentState(ApartmentState.STA);
altThread1.Priority = ThreadPriority.AboveNormal;
altThread1.IsBackground = true;
altThread1.Start();
}
public void AltThread1Proc()
{
MessageBox.Show("Hello on Alt Thread");
CustomMessageBox.Show("Hello on alt thread");
}
}
The problems occur in AltThreadProc() with CustomMessageBox.Show(...). The curious behavior I referred to is this: If you hit the main thead button and then the Alt thread button, you get the error:
Cannot access Freezable 'System.Windows.Media.SolidColorBrush' across threads because it cannot be frozen.
However, if you skip the main thread button and just hit the Alt thread button, you get the error:
The calling thread cannot access this object because a different thread owns it.
I'm curious what the "Freezable" error is all about and why you can get different errors based on what would seem to be an innocuous event: clicking/not clicking button that produces message box on main thread.
Ideally, it would be nice to just replace System.Windows.MessageBox with Xceed.Wpf.Toolkit.MessageBox, but if there is some sort of extra code to write, that might be acceptable. The documentation, and the link I provided hints at using a WindowContainer, but I can't really see any examples of how you do that. I was attracted to the Toolkit MessageBox as it allows one to do some cool stuff with MessageBox (which I don't show here) such as apply styles, change the text of the OK, CANCEL button, etc.
Any ideas would be much appreciated.
Thanks,
Dave
Extra info:
User1341210 suggestion works well if you just have one window. However, if you have a second window in it's own thread it doesn't work so well. Perhaps someone can tell me what I'm doing wrong. I use the suggestion of the TaskScheduler, but the code throws an exception if the TaskScheduler used is the one of the second window. That is, all works fine if I use the TaskScheduler of the first window, but throws an exception if I use the TaskScheduler of the second window. Here is the code behind for my second window:
public partial class AltThreadWindow : Window
{
private TaskScheduler _ui;
public AltThreadWindow()
{
InitializeComponent();
_ui = TaskScheduler.FromCurrentSynchronizationContext();
}
// This constructor is for passing in the TaskScheduler of the mainwindow and works great
public AltThreadWindow(TaskScheduler scheduler)
{
InitializeComponent();
_ui = scheduler;
}
private void btnWindowsMsgBox_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Standard Windows message box");
}
private void btnCustomMsgBox_Click(object sender, RoutedEventArgs e)
{
MessageBoxResult result;
Task.Factory.StartNew(() => { result = CustomMessageBox.Show("Custom MessageBox on separate window"); }, CancellationToken.None,
TaskCreationOptions.None,
_ui);
}
}
Notice the two constructors. The default one assigns the TaskScheduler of the second window. The other constructor allows one to pas in the TaskScheduler of the main Window.
Here's the code I use to launch the second window from the main window. Again, I'm launching the second window on another thread, and I pass in the TaskScheduler of the main window. It would be nice to use the TaskScheduler of the second window instead.
_altWindowThread = new Thread(new ThreadStart(AltWinThreadProc));
_altWindowThread.SetApartmentState(ApartmentState.STA);
_altWindowThread.Priority = ThreadPriority.AboveNormal;
_altWindowThread.IsBackground = true;
_altWindowThread.Start();
And the actual threadproc:
[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
public void AltWinThreadProc()
{
// Create our context, and install it:
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
_altWindow = new AltThreadWindow(_ui);
_altWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
Notice here I pass in the TaskScheduler of the MainWindow.
we had the same issue in our application (I created the work item on codeplex).
The error messages are quite confusing and I cant provide you an answer to that.
But:
We didn't used a separated WindowContainer to solve it. Instead came up with calling the separate task/thread with the UI scheduler:
Task.Factory.StartNew(
() => { result = CustomMessageBox.Show(messageText); },
CancellationToken.None,
TaskCreationOptions.None,
_ui);
Where _ui is assigned in a method that is executed from UI context (e.g. Constructor of your Window/Control:
_ui = TaskScheduler.FromCurrentSynchronizationContext();
Hope this helps for solving the "replace System.Windows.MessageBox with Xceed.Wpf.Toolkit.MessageBox" part of your question.
If you want that the messagebox shows up on another Window you have to set the "Owner" property of the message box to the other window.
Best regards.

Drag a custom activity in a re-hosted workflow designer

Hello
I have a problem in my Design which is in this http://archive.msdn.microsoft.com/wfxbap/Release/ProjectReleases.aspx?ReleaseId=4668
I have made a custom activity in my Re-hosted workflow designer in WPF, and I make this custom activity shows in the end-user toolbar with the other activites, but the custom one can't be drop into the sequence at all like other activities.
I put the AllowDrop="True" in the .XAML file and the following in the .cs file:
protected override void OnDragEnter(DragEventArgs e)
{
//Check the object is actually something we want to be droppable
if (DragDropHelper.AllowDrop(
e.Data,
this.Context,
typeof(Activity)))
{
e.Effects = (DragDropEffects.Move & e.AllowedEffects);
e.Handled = true;
}
base.OnDragEnter(e);
}
protected override void OnDragOver(DragEventArgs e)
{
//Check the object is actually something we want to be droppable
if (DragDropHelper.AllowDrop(
e.Data,
this.Context,
typeof(Activity)))
{
e.Effects = (DragDropEffects.Move & e.AllowedEffects);
e.Handled = true;
}
base.OnDragOver(e);
}
protected override void OnDrop(DragEventArgs e)
{
//droppedItem - may be a ModelItem or a newly instantiated object (from toolbox)
object droppedItem = DragDropHelper.GetDroppedObject(this, e, this.Context);
ModelItem canvasActivity = this.ModelItem;
canvasActivity.Properties["Children"].Collection.Add(droppedItem);
e.Handled = true;
DragDropHelper.SetDragDropCompletedEffects(e, DragDropEffects.Move);
base.OnDrop(e);
}
Any help please?
I realize this post is old but I just ran across this problem as well so thought I would post for anyone else who stumbles upon this. I found that adding your custom Activity library to the same binary path as your rehosted designer should do the trick.

Is it possible to show/hide UserControls within a Silverlight XAP file from JavaScript?

I've created a Silverlight project that produces [something].xap file to package a few silverlight UserControls. I would like to manipulate that .xap file through the use of javascript in the browser to show and hide user controls based upon java script events.
Is it possible to do this?
If so any sample could or links to documentation would be appreciated.
Thanks in advance
Kevin
Here's my solution...not sure if it's the "best-practices" way...comments????
In the App class within my Silverlight application I have the following code:
private Page _page = null;
private void Application_Startup(object sender, StartupEventArgs e)
{
_page = new Page();
this.RootVisual = _page;
HtmlPage.RegisterScriptableObject("App", this);
}
Also to the App class I add a [ScriptableMember] to be called from JavaScript
[ScriptableMember]
public void ShowTeamSearch(Guid ctxId, Guid teamId)
{
_page.ShowTeamSearcher(ctxId, teamId);
}
The Page class is the default one that get's created within the Silverlight Control project, it really doesn't have any UI or logic, it's just used to swap in/out the views.
Login oLogin;
TeamSearcher oSearcher;
public Page()
{
InitializeComponent();
oLogin = new Login();
oSearcher = new TeamSearcher();
oLogin.Visibility = Visibility;
this.LayoutRoot.Children.Add(oLogin);
}
Also a method is added to show/hide the views...this could/will probably get more advanced/robust with animations etc...but this shows the basic idea:
public void ShowTeamSearcher(Guid ctxId, Guid teamId)
{
oSearcher.UserTeamId = teamId;
oSearcher.UserContextId = ctxId;
LayoutRoot.Children.Remove(oLogin);
LayoutRoot.Children.Add(oSearcher);
}
Then to invoke this in the JavaScript after assigning the id of oXaml to the instance of the silverlight host.
var slControl = document.getElementById('oXaml');
slControl.Content.App.ShowTeamSearch(sessionId, teamId);
This seems to work and isn't all that bad of a solution, but there might be something better...thoughts?
Here is a my collections of my links for this subject.
Javascript communication to
Silverlight 2.0
Silverlight
interoperability
Silverlight
and JavaScript Interop Basics

Resources