If I have two windows in an oob application how do I communicate between them?
This is the new feature of silverlight 5 that allows for multiple windows.
They run in a common application. Hence they share the same static data. The scope of communication choices are therefore very large. Here is an example:-
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(object payload)
{
Payload = payload;
}
public object Payload {get; private set; }
}
public class Messenger
{
private static readonly Messenger _current = new Messenger();
public static Messenger Current { get { return _current; } }
public event EventHandler<MessageEventArgs> MessageReceived;
public void Send(object payload)
{
if (MessageReceived != null)
MessageReceived(this, new MessageEventArgs(payload));
}
}
All windows can attach a handler to Messenger.Current.MessageReceived (just be sure to detach when the window closes) and any window can call Messenger.Current.Send.
Ok so you wouldn't actually use this code its a bit rubbish, the point is Windows in SL5 are not isolated. You can create whatever internal application communication mechanism you need.
Option 1: MVVM Pattern
Both windows share a reference to the same view-model. Changes made by one are seen by both.
Option 2: Normal references
Window A can how a refernce to Windows B when it creates it.
Option 3: Message Passing
You can have a global event that you subscribe to in the Load event. (Make sure you unsubscribe in the Unload event or you will leak memory!) Windows can post messages to that event which the other windows listen for.
Related
I am writing an application using Prism that contains three modules. First one has a view to configure a "Person", second one is a service that generates that "Person" and third one is the visualization of all people. These three modules communicate with EventAggregator system. But I have problems with the messages on the service one.
In this service module I only have the service implementation and the module definition.
This service is a people manager that receives a message from EventAggregator, creates a "Person" with a task and send a message to the third module with this "Person".
Service:
private List<Person> people = new();
public PeopleControllerService(IEventAggregator eventAggregator, ICommonParametersService commonParameters)
{
this._eventAggregator = eventAggregator;
eventAggregator.GetEvent<GeneratePersonEvent>().Subscribe(GeneratePerson);
this._commonParameters = commonParameters;
}
private void GeneratePerson()
{
Person newPerson = new(this._commonParameters.DefaultPersonTask);
this.People.Add(newPerson);
this._eventAggregator.GetEvent<AssignedPersonEvent>().Publish(newPerson);
}
Module definition:
private PeopleControllerService moduleController;
public void OnInitialized(IContainerProvider containerProvider)
{
IEventAggregator eventAggregator = containerProvider.Resolve<IEventAggregator>();
ICommonParametersService commonParametersService = containerProvider.Resolve<ICommonParametersService>();
this.moduleController = new(eventAggregator, commonParametersService);
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
}
The problem is that when I send the "GeneratePersonEvent" message it never reaches the PeopleControllerService and the "GeneratePerson" method is never executed.
I've tried using a view and a viewModel, programming the service in the viewModel and assigning the view to a dummy and hidden region in the app and I've verified that it works that way.
Modified module definition:
public void OnInitialized(IContainerProvider containerProvider)
{
IRegionManager regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.DummyRegion, "PeopleController");
}
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterForNavigation<PeopleController>();
}
How can I use the EventAggregator without using a dummy view? Do I have to add something in the "RegisterTypes" method? I've tried with:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<PeopleControllerService>();
}
but it doesn't work either.
I've checked this post: Can I get EventAggregator Subscribe Message without view, viewmodel in prism?, and there it says that it is possible, but doesn't describe how to implement.
Most of the time you want exactly one instance of a service, and you have to tell the container:
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.RegisterSingleton<PeopleControllerService>();
}
Also, you want your service to implement an interface so that you can pass different implementations to the consumers of your service, the most obvious case is your tests.
You need to actually create the instance of your service, too. Normally, you inject it into some consumer, but if it's completely decoupled and only talks through the event aggregator, you have to create the instance manually:
// in App.xaml.cs
protected override void OnInitialized()
{
Container.Resolve<PeopleControllerService>();
base.OnInitialized();
}
Hint: if the service implements an interface, the application doesn't need to personally know the controller module.
I am in need of help.
I have created a dockable WPF within Revit.
It is working well and I can 'show' & ;hide' from push buttons.
My aim is to create buttons within the WPF that run custom commands.I dont need to interact or show any information within the WPF, its purely just acting as a push button but in the WPF instead of a ribbon.
The commands currently work and can be executed via the Add-In Manager.
Below is the command I am trying to run:
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using System.Collections.Generic;
using System.Linq;
namespace Adams.Commands
{
[Transaction(TransactionMode.Manual)]
[Regeneration(RegenerationOption.Manual)]
public class PrecastDisallowJoin : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
var uiApplication = commandData.Application;
var application = uiApplication.Application;
var uiDocument = uiApplication.ActiveUIDocument;
var document = uiDocument.Document;
// Prompt the user to select some walls
var references = uiDocument.Selection
.PickObjects(
ObjectType.Element,
new WallSelectionFilter(),
"Please select walls");
var components = references.Select(r => document.GetElement(r)).ToList();
// Start a transaction
using (Transaction t = new Transaction(document, "Change Wall Join Behavior"))
{
t.Start();
// Loop through the selected walls and change their join behavior
foreach (Reference reference in references)
{
Wall wall = document.GetElement(reference) as Wall;
WallUtils.DisallowWallJoinAtEnd(wall, 0);
WallUtils.DisallowWallJoinAtEnd(wall, 1);
}
// Commit the transaction
t.Commit();
}
return Result.Succeeded;
}
public class WallSelectionFilter : ISelectionFilter
{
public bool AllowElement(Element elem)
{
//return elem is FamilyInstance;
return elem.Name.Contains("Precast");
}
public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
}
}
}
My XAML.cs looks like this:
using Autodesk.Revit.UI;
using System.Windows.Controls;
using Adams.Commands;
using System.Windows;
namespace Adams.ui
{
public partial class Customers : UserControl
{
public UIDocument uIDocument { get; }
public ExternalCommandData commandData { get; }
public Customers(UIDocument uIDocument )
{
InitializeComponent();
}
private void btnStartExcelElementsApp_Click(object sender, RoutedEventArgs e)
{
string message = string.Empty;
PrecastDisallowJoin precastDisallow = new PrecastDisallowJoin();
precastDisallow.Execute(commandData, ref message, null);
}
}
}
Any ideas of what i should be trying?
I'm new to creating add-ins and appreciate any help offered.
If I have missed any critical info please let me know.
Thank you all
When I tried the above it crashes Revit.
Im not sure how to pass the required information in the Execute method in the XAML.
The Revit dockable dialogue and hence your WPF form lives in a modeless context. It does not execute within a valid Revit API context. A valid Revit API context is only provided by Revit itself, within the event handlers called by Revit when specific events are raised. For instance, clicking a button to launch an add-in external command raises the IExternalCommand.Execute event.
The Building Coder shares a long list of articles on Idling and External Events for Modeless Access and Driving Revit from Outside
explaining how to gain access to a valid Revit API context from a modeless state.
You can address your task by using an external event:
Idling Enhancements and External Events
External Command Lister and Adding Ribbon Commands
External Event and 10 Year Forum Anniversary
Implementing the TrackChangesCloud External Event
Vipassana and Idling versus External Events
The question has also been discussed many times in the Revit API discussion forum, so you can check there for threads including WPF, dockable and external event.
You can use IExternalEventHandler:
public class MyExternalEvent : IExternalEventHandler
{
public void Execute(UIApplication app)
{
//do your revit related stuff here
}
public string GetName()
{
return "xxx";
}
}
Create external event:
ExternalEvent myExEvent= ExternalEvent.Create(new MyExternalEvent());
In order to effectively use the above you will have to hold reference to "myExEvent" in some ViewModelClass then you will be able to raise this event inside your xaml.cs:
ViewModelClass.TheEvent = myExEvent;
ViewModelClass.TheEvent.Raise();
EDIT: What you were trying to do is unfortunately not acceptable with revit API. WPF window displayed as dockpanel does not have access to valid revit api context. IExternalEventHandler gives you the possibility to somehow link dockpanel user interface with revit api.
I've written a WPF app that has two different main windows. I don't know which one to launch until runtime by looking up what kind of user is using the program in a database. The code I currently have works but Castle Windsor is doing tons of extra work by newing up the object graphs for both kinds of windows.
private readonly IMainWindow _mainWindow;
private readonly ISimplifiedMainWindow _simplifiedMainWindow;
public MainClass(
IMainWindow mainWindow,
ISimplifiedMainWindow simplifiedMainWindow)
{
_mainWindow = mainWindow;
_simplifiedMainWindow = simplifiedMainWindow;
}
public RunApp()
{ // pseudocode
if (user is fullUser) _mainWindow.Show();
else _simplifiedMainWindow.Show();
}
How do I defer creation of my window objects without resorting to making an abstract factory that will basically duplicate what Castle Windsor does anyway?
A factory is in fact the solution I'd recommend (and a solution I've successfully used multiple times in the past to solve this very problem).
I wouldn't implement the factory myself though, let Windsor do it (via a Typed Factory).
public interface IWindowFactory
{
IMainWindow FullUserWindow();
ISimplifiedMainWindow SimplifiedUserWindow();
//optionally
void DestroyWindow(IWindow window);
}
Now you just need to tell Windsor to build a factory for that interface
container.AddFacility<TypedFactoryFacility>();
// later on, in your installer
container.Register(Component.For<IWindowFactory>()
.AsFactory()
.LifestyleTransient());
and your app code changes to:
public RunApp()
{ // pseudocode
if (user is fullUser) Show(factory.FullUserWindow());
else Show(factory.SimplifiedUserWindow());
}
I have a WPF application which calls WCF service methods through a Client which exposes these methods. Is there any way to bind my application to a property of the service, and to get notified when this property changes? I know INotifyPropertyChanged but I have some doubts about its efficiency in this case... Thanks
EDIT : Actually, all I want is my application to be notified of the changes that happen on the server side.
There are a couple of questsions here. You can bind your code to the client end of a WCF service and by using a partial class definition you can add an INotifyPropertyChanged interface to it so that it meets your design. But actually wiring up the mechanism for pushing updates from the server would be much harder.
In fact, Events will work over WCF, and reasonably performant i.e. you won't have the delay associated with polling. However I wouldn't try to squeeze your WCF code into fitting the INotifyPropertyChanged pattern. Instead use a more bespoke interface for the client/server comms and then expose the INotifyPropertyChanged back in the ViewModel.
Just add a delegate to your service, then call the service from your view model or code behind and reflect the changes with your properties that implement the INotifyPropertyChanged interface:
In Service:
public delegate void ServcieUpdate(SomeDataType data);
public ServcieUpdate OnServcieUpdated { get; set; }
When data is updated:
if (OnServcieUpdated != null) OnServcieUpdated(data);
In view model:
private ServiceClient serviceClient = new ServiceClient();
private ObservableCollection<SomeDataType> data = new
ObservableCollection<SomeDataType>();
public YourViewModel()
{
serviceClient.OnServiceUpdated += OnServcieUpdated;
}
public ObservableCollection<SomeDataType> Data
{
get { return data; }
set { data = value; NotifyPropertyChanged("Data");
}
public void OnServcieUpdated(SomeDataType data)
{
Data = data;
}
Please take a look at the Delegates (C# Programming Guide) page on MSDN in you are unfamiliar with using delegate objects.
I am using Ninject as DI container in a Silverlight application. Now I am extending the application to support interception and started integrating DynamicProxy2 extension for Ninject. I am trying to intercept call to properties on a ViewModel and ending up getting following exception:
“Attempt to access the method failed: System.Reflection.Emit.DynamicMethod..ctor(System.String, System.Type, System.Type[], System.Reflection.Module, Boolean)”
This exception is thrown when invocation.Proceed() method is called. I tried two implementations of the interceptor and they both fail
public class NotifyPropertyChangedInterceptor: SimpleInterceptor
{
protected override void AfterInvoke(IInvocation invocation)
{
var model = (IAutoNotifyPropertyChanged)invocation.Request.Proxy;
model.OnPropertyChanged(invocation.Request.Method.Name.Substring("set_".Length));
}
}
public class NotifyPropertyChangedInterceptor: IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
var model = (IAutoNotifyPropertyChanged)invocation.Request.Proxy;
model.OnPropertyChanged(invocation.Request.Method.Name.Substring("set_".Length));
}
}
I want to call OnPropertyChanged method on the ViewModel when property value is set.
I am using Attribute based interception.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class NotifyPropertyChangedAttribute : InterceptAttribute
{
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
if(request.Method.Name.StartsWith("set_"))
return request.Context.Kernel.Get<NotifyPropertyChangedInterceptor>();
return null;
}
}
I tested the implementation with a Console Application and it works alright.
I also noted in Console Application as long as I had Ninject.Extensions.Interception.DynamicProxy2.dll in same folder as Ninject.dll I did not have to explicitly load DynamicProxy2Module into the Kernel, where as I had to explicitly load it for Silverlight application as follows:
IKernel kernel = new StandardKernel(new DIModules(), new DynamicProxy2Module());
Could someone please help? Thanks
Reflection can be really tricky in silverlight because of security issues.
Check Gabe's answer for this question, it's the same problem.
The good news is that you can achieve the same functionality you want using dynamic instead of proxies. Just extend your ViewModel from DynamicObject and override the TrySetMember method.
I hope it helps :)