Open other window from window - wpf

I have some academic question here. I read this question WPF MVVM Get Parent from VIEW MODEL and concluded that ViewModel should not opens any windows itself. So I use Messenger now to send message to ViewModel's Window and Window opens other window - NewWindow. It works fine, but what if NewWindow does something and get some Result has to be passed in MainWindow for further actions? More detailed:
NewWindow opened by button click in Window (OpenNewWindowCommand) and made some calculations.
After calculations NewWindow got some Result (does't matter what exactly is it) and rise a corresponding event - GotSomeResult, where event arg is Result.
This Result has to be passed in MainWindow to further processing, so I bind event handler to GotSomeResult event.
Below you can see all required code to illustrate this scenario.
MainWindow code-behind:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
Messenger.Default.Register<NewWindowMessage>(this, OpenNewWindow);
}
private void OpenNewWindow(NewWindowMessage message)
{
var newWindow = new NewWindow();
var newWindowViewModel = (NewWindowViewModel) message.Target;
newWindowViewModel.GotSomeResult += ((MetaWindowViewModel)DataContext).ProcessResult;
newWindow.Owner = this;
newWindow.DataContext = newWindowViewModel;
newWindow.ShowDialog();
}
MainWindow ViewModel:
public void OpenNewWindowCommand()
{
Messenger.Default.Send(new NewWindowMessage(this, new NewWindowViewModel("OpenWindow"), String.Empty));
}
public void ProcessResult(Object someResult)
{
// Any actions with result
}
newWindowViewModel.GotSomeResult += ((MetaWindowViewModel)DataContext).ProcessResult; --- this string seems problem for me. Is it correct to get access to public method of ViewModel right in theView? Does it violent MVVM pattern?

Why don't you hook the handler to GotSomeResult at the VM level, ie :
public void OpenNewWindowCommand()
{
var newWindowViewModel = new NewWindowMessage(this, new NewWindowViewModel("OpenWindow"), String.Empty)
newWindowViewModel.GotSomeResult += this.ProcessResult;
Messenger.Default.Send();
}
It removes the references to your ViewModel in your codebehind (which indeed should be avoided):
private void OpenNewWindow(NewWindowMessage message)
{
var newWindow = new NewWindow();
newWindow.Owner = this;
newWindow.DataContext = message.Target;
newWindow.ShowDialog();
}

Related

Winforms WebBrowser control without IE popups not appearing [duplicate]

I am trying to implement a simple web browser control in one of my apps. This is to help integrate a web app into a toolset i am creating.
The problem is, this web app absolutly loves popup windows....
When a popup is opened, it opens in an IE window which is not a child of the MDI Container form that my main window is part of.
How can i get any and all popups created by clicking links in my WebBrowser to be a child of my MDI container (similar to setting the MDIParent property of a form)?
Thanks in advance.
The web browser control supports the NewWindow event to get notified about a popup window. The Winforms wrapper however does not let you do much with it, you can only cancel the popup. The native COM wrapper permits passing back a new instance of the web browser, that instance will then be used to display the popup.
Taking advantage of this requires some work. For starters, use Project + Add Reference, Browse tab and select c:\windows\system32\shdocvw.dll. That adds a reference to the native COM interface.
Create a form that acts as the popup form. Drop a WebBrowser on it and make its code look similar to this:
public partial class Form2 : Form {
public Form2() {
InitializeComponent();
}
public WebBrowser Browser {
get { return webBrowser1; }
}
}
The Browser property gives access to the browser that will be used to display the web page in the popup window.
Now back to the main form. Drop a WebBrowser on it and make its code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
webBrowser1.Url = new Uri("http://google.com");
}
SHDocVw.WebBrowser nativeBrowser;
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
nativeBrowser = (SHDocVw.WebBrowser)webBrowser1.ActiveXInstance;
nativeBrowser.NewWindow2 += nativeBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e) {
nativeBrowser.NewWindow2 -= nativeBrowser_NewWindow2;
base.OnFormClosing(e);
}
void nativeBrowser_NewWindow2(ref object ppDisp, ref bool Cancel) {
var popup = new Form2();
popup.Show(this);
ppDisp = popup.Browser.ActiveXInstance;
}
}
The OnLoad method obtains a reference to the native COM interface, then subscribes an event handler to the NewWindow2 event. I made sure to unsubscribe that event in the FormClosing event handler, not 100% sure if that's necessary. Better safe then sorry.
The NewWindow2 event handler is the crux, note that the first argument allows passing back an untyped reference. That should be the native browser in the popup window. So I create an instance of Form2 and Show() it. Note the argument to Show(), that ensures that the popup is an owned window. Substitute this as necessary for your app, I assume you'd want to create an MDI child window in your case.
Do beware that this event doesn't fire for the window displayed when Javascript uses alert(). The browser doesn't treat that window as an HTML popup and doesn't use a browser window to display it so you cannot intercept or replace it.
I found that the best way to do this was to implement/sink the NewWindow3 event
Add the reference to c:\windows\system32\shdocvw.dll as mentioned in the other answers here.
Add event handler
SHDocVw.WebBrowser wbCOMmain = (SHDocVw.WebBrowser)webbrowser.ActiveXInstance;
wbCOMmain.NewWindow3 += wbCOMmain_NewWindow3;
Event method
void wbCOMmain_NewWindow3(ref object ppDisp,
ref bool Cancel,
uint dwFlags,
string bstrUrlContext,
string bstrUrl)
{
// bstrUrl is the url being navigated to
Cancel = true; // stop the navigation
// Do whatever else you want to do with that URL
// open in the same browser or new browser, etc.
}
Set "Embed Interop Types" for the "Interop.SHDocVw" assembly to false
Set the "local copy" to true.
Source for that help MSDN Post
Refining Hans answer, you can derive the WebBrowser for accessing the COM without adding the reference. It is by using the unpublished Winforms WebBrowser.AttachInterface and DetachInterface methods.
More elaborated here.
Here is the code:
Usage (change your WebBrowser instance to WebBrowserNewWindow2)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.webBrowser1.NewWindow2 += webBrowser_NewWindow2;
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
webBrowser1.NewWindow2 -= webBrowser_NewWindow2;
base.OnFormClosing(e);
}
void webBrowser_NewWindow2(object sender, WebBrowserNewWindow2EventArgs e)
{
var popup = new Form1();
popup.Show(this);
e.PpDisp = popup.Browser.ActiveXInstance;
}
public WebBrowserNewWindow2 Browser
{
get { return webBrowser1; }
}
}
Code:
using System;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace SHDocVw
{
public delegate void WebBrowserNewWindow2EventHandler(object sender, WebBrowserNewWindow2EventArgs e);
public class WebBrowserNewWindow2EventArgs : EventArgs
{
public WebBrowserNewWindow2EventArgs(object ppDisp, bool cancel)
{
PpDisp = ppDisp;
Cancel = cancel;
}
public object PpDisp { get; set; }
public bool Cancel { get; set; }
}
public class WebBrowserNewWindow2 : WebBrowser
{
private AxHost.ConnectionPointCookie _cookie;
private WebBrowser2EventHelper _helper;
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void CreateSink()
{
base.CreateSink();
_helper = new WebBrowser2EventHelper(this);
_cookie = new AxHost.ConnectionPointCookie(
this.ActiveXInstance, _helper, typeof(DWebBrowserEvents2));
}
[PermissionSetAttribute(SecurityAction.LinkDemand, Name = "FullTrust")]
protected override void DetachSink()
{
if (_cookie != null)
{
_cookie.Disconnect();
_cookie = null;
}
base.DetachSink();
}
public event WebBrowserNewWindow2EventHandler NewWindow2;
private class WebBrowser2EventHelper : StandardOleMarshalObject, DWebBrowserEvents2
{
private readonly WebBrowserNewWindow2 _parent;
public WebBrowser2EventHelper(WebBrowserNewWindow2 parent)
{
_parent = parent;
}
public void NewWindow2(ref object pDisp, ref bool cancel)
{
WebBrowserNewWindow2EventArgs arg = new WebBrowserNewWindow2EventArgs(pDisp, cancel);
_parent.NewWindow2(this, arg);
if (pDisp != arg.PpDisp)
pDisp = arg.PpDisp;
if (cancel != arg.Cancel)
cancel = arg.Cancel;
}
}
[ComImport, Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch),
TypeLibType(TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
[DispId(0xfb)]
void NewWindow2(
[In, Out, MarshalAs(UnmanagedType.IDispatch)] ref object ppDisp,
[In, Out] ref bool cancel);
}
}
}
I know the question is very old but I solved it this way: add new reference, in COM choose Microsoft Internet Controls and in the code, before the click that opens a new window add the following:
SHDocVw.WebBrowser_V1 axBrowser = (SHDocVw.WebBrowser_V1)webBrowser1.ActiveXInstance;
axBrowser.NewWindow += axBrowser_NewWindow;
and then add the following method:
void axBrowser_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
Processed = true;
webBrowser1.Navigate(URL);
}

Apply DI by using unity in WPF application

I got confused after reading the documentation of Unity framework.
link
I'am writing a WPF application which will search for some devices.
Below my code from my main Window.
As you can see, now i'am still declaring UnitOfWork and DeviceService inside my main Window. I want to replace this code by applying Dependency Injection. At the same time i would also inject my viewmodel inside my main window.
public Window1()
{
InitializeComponent();
UnitOfWork myUnitOfWork = new UnitOfWork();
DeviceService dService = new DeviceService(myUnitOfWork);
_vm = new DeviceViewModel(dService);
this.DataContext = _vm;
_vm.SearchAll();
}
I gave a try in below code but i failed in setting the container. The real question is how should i start? Do i need to completely change the stucture of my program?
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
UnitOfWork myUnitOfWork = new UnitOfWork();
container.RegisterInstance<UnitOfWork>(myUnitOfWork);
Window1 w1 = new Window1();
w1.Show();
}
}
I went trough the suggested tutorial. It is still not clear for me on how i should configure the property injection.
My viewmodel should be injected in the Window 1 class, so i assume that i have to create a dependency property.
private DeviceViewModel viewModel;
[Dependency]
public DeviceViewModel ViewModel
{
get { return viewModel; }
set { this.DataContext = value; }
}
How can i inject my viewmodel into window 1, knowing that DeviceViewModel has dependency on DeviceService and again on UnitOfWork ?
//CONSTRUCTOR
public DeviceViewModel(DeviceService service)
{
Service = service;
SearchCommand = new SearchCommand(this);
}
private UnitOfWork myUnit;
public DeviceService(UnitOfWork unit)
{
myUnit = unit;
}
You need to tell container how to build all the objects needed by other objects, then the container will instantiate whatever is needed when needed.
your property injection is only missing one line:
private DeviceViewModel viewModel;
[Dependency]
public DeviceViewModel ViewModel
{
get { return viewModel; }
set { viewModel = value; this.DataContext = viewModel; }
}
Then on you OnStartup()
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
IUnityContainer container = new UnityContainer();
container.RegisterType<UnitOfWork>();
container.RegisterType<DeviceService>();
container.RegisterType<DeviceViewModel>();
Window1 w1 = container.Resolve<Window1>();
w1.Show();
}
There are different parameters you can use in RegisterType(), so you can control the lifetime and the creation of your objects.
You need to go through this example: http://visualstudiogallery.msdn.microsoft.com/3ab5f02f-0c54-453c-b437-8e8d57eb9942
You are on the right tracks, just you should resolve the window, not new it up.
//instead of Window1 w1 = new Window1();
Window1 w1 = container.Resolve<Window1>();
w1.DataContext = container.Resolve<DeviceViewModel>();
Window1 will no longer need to set its own DataContext

Read Control Value from backgroundworker thread in WPF

I'm trying to read the values of the controls from a backgroundworker.
There are plenty of examples of how to update a control from a background thread, but i've not been able to find one example of how to read from it.
At the moment I have created a struct to pass to the backgroundworker, but it does not seem very elegant.
Struct:
public struct Arguments
{
public string version;
public bool isChecked;
public Arguments(string Version, bool IsChecked)
{
version = Version;
isChecked = IsChecked;
}
}
Button Click Event
Arguments pass = new Arguments(Version.Text.Trim(), (bool)Credentials.IsChecked);
export.RunWorkerAsync(pass);
DoWork Event
ExportSolution export = new ExportSolution(this, messageBox);
Arguments pass = (Arguments)e.Argument;
export.Export(pass.version, pass.isChecked);
This works fine, but I would like to do the following or something similar
DoWork event
ExportSolution export = new ExportSolution(this, messageBox);
export.Export(Access Version.Text.Trim() from here, ditto (bool)Credentials.IsChecked);
TIA
This is what I've ended up doing:
string version;
bool isChecked;
public MainWindow()
{
InitializeComponent();
this.DataContext = validator;
//export.DoWork +=new DoWorkEventHandler(export_DoWork);
export.DoWork += (sender, args) =>
{
ExportSolution exports = new ExportSolution(this, messageBox);
Dispatcher.Invoke((Action)(() => version = Version.Text.Trim()));
Dispatcher.Invoke((Action)(() => isChecked = (bool)Credentials.IsChecked));
exports.Export(version, isChecked);
};
}
Where Version is a textbox and Credentials is a Checkbox.
If you are working on Code Behind, you can do the DoWork event as a Lambda expression,
eg.
DoWork+=(_,_)=>{...}
then inside the labda expression you can see the outside scope, and you can see the windows control's names, and the buttob.

Open new Window from view model

Hi I have a beginner problem. I have shell (it is wpf window) and in this shell is screen (it is an user control / view model).
I would like open new window from view model, not show user control in shell.
So I create new window - ChatView
<Window x:Class="Spirit.Views.ChatView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:extToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended" Title="ChatView" Height="545" Width="763">
<Grid Margin="4,4,4,4">
</Grid>
</Window>
Export ChatViewModel with MEF.
public interface IChatViewModel
{
}
[Export("ChatScreen",typeof(IChatViewModel))]
public class ChatViewModel
{
}
In view model I have this method:
With ShowScreen class help me Mr.Marco Amendola. It look likes this:
public class ShowScreen : IResult
{
readonly Type _screenType;
readonly string _name;
[Import]
public IShellViewModel Shell { get; set; }
Action<object> _initializationAction = screen => { };
public ShowScreen InitializeWith<T>(T argument)
{
_initializationAction = screen =>
{
var initializable = screen as IInitializable<T>;
if (initializable != null)
initializable.Initialize(argument);
};
return this;
}
public ShowScreen(string name)
{
_name = name;
}
public ShowScreen(Type screenType)
{
_screenType = screenType;
}
public void Execute(ActionExecutionContext context)
{
var screen = !string.IsNullOrEmpty(_name)
? IoC.Get<object>(_name)
: IoC.GetInstance(_screenType, null);
_initializationAction(screen);
Shell.ActivateItem(screen);
Completed(this, new ResultCompletionEventArgs());
}
public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
public static ShowScreen Of<T>()
{
return new ShowScreen(typeof(T));
}
}
My problem is if I try show new window it doesn’t works, it works only if I show new user control in shell(window).
I would like achieve behavior something like in skype. You have a main window with listbox, you double clicked on item and it show new chat window.
Main window can publish with EventAggregator on chat window and also chat window can publish on main window. This is my goal.
I know that I can not use class ShowScreen on showing new Window. I would like to know what is correct way to create new window from view model and inject event aggregator
to this vie model.
Any advice? Thank for your help and time.
Have you looked at WindowManager.Show or WindowManager.ShowDialog? Rob has a sample at http://caliburnmicro.codeplex.com/wikipage?title=The%20Window%20Manager. You can inject this dependency into your view model as IWindowManager.
I'm using this. Maybe could save a question about "where's the code ?".
DialogHelper:
public class DialogHelper
{
public void ShowDialog<T>(params Object[] param) where T : class
{
var windowManager = new WindowManager();
T viewModel = Activator.CreateInstance(typeof(T), param) as T;
windowManager.ShowWindow(viewModel);
}
}
How to use:
Without constructor parameter:
Dialog.ShowDialog<TestTableViewModel>();
With constructor paramater:
Dialog.ShowDialog<TestTableViewModel>(dt);
Note that I'm not using MEF

Silverlight DataBinding cross thread issue

I have an Image control with it's source bound to a property on an object(string url to an image). After making a service call, i update the data object with a new URL. The exception is thrown after it leaves my code, after invoking the PropertyChanged event.
The data structure and the service logic are all done in a core dll that has no knowledge of the UI. How do I sync up with the UI thread when i cant access a Dispatcher?
PS: Accessing Application.Current.RootVisual in order to get at a Dispatcher is not a solution because the root visual is on a different thread(causing the exact exception i need to prevent).
PPS: This only is a problem with the image control, binding to any other ui element, the cross thread issue is handled for you.
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(() => {...});
Also look here.
Have you tried implementing INotifyPropertyChanged?
The property getter for RootVisual on the Application class has a thread check which causes that exception. I got around this by storing the root visual's dispatcher in my own property in my App.xaml.cs:
public static Dispatcher RootVisualDispatcher { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new Page();
RootVisualDispatcher = RootVisual.Dispatcher;
}
If you then call BeginInvoke on App.RootVisualDispatcher rather than Application.Current.RootVisual.Dispatcher you shouldn't get this exception.
I ran into a similar issue to this, but this was in windows forms:
I have a class that has it's own thread, updating statistics about another process, there is a control in my UI that is databound to this object. I was running into cross-thread call issues, here is how I resolved it:
Form m_MainWindow; //Reference to the main window of my application
protected virtual void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
if(m_MainWindow.InvokeRequired)
m_MainWindow.Invoke(
PropertyChanged, this, new PropertyChangedEventArgs(propertyName);
else
PropertyChanged(this, new PropertyChangedEventArgs(propertyName);
}
This seems to work great, if anyone has suggestions, please let me know.
When ever we want to update UI related items that action should happen in the UI thread else you will get an invalid cross thread access exception
Deployment.Current.Dispatcher.BeginInvoke( () =>
{
UpdateUI(); // DO the actions in the function Update UI
});
public void UpdateUI()
{
//to do :Update UI elements here
}
The INotifyPropertyChanged interface is used to notify clients, typically binding clients, that a property value has changed.
For example, consider a Person object with a property called FirstName. To provide generic property-change notification, the Person type implements the INotifyPropertyChanged interface and raises a PropertyChanged event when FirstName is changed.
For change notification to occur in a binding between a bound client and a data source, your bound type should either:
Implement the INotifyPropertyChanged interface (preferred).
Provide a change event for each property of the bound type.
Do not do both.
Example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Windows.Forms;
// Change the namespace to the project name.
namespace TestNotifyPropertyChangedCS
{
// This form demonstrates using a BindingSource to bind
// a list to a DataGridView control. The list does not
// raise change notifications. However the DemoCustomer type
// in the list does.
public partial class Form1 : Form
{
// This button causes the value of a list element to be changed.
private Button changeItemBtn = new Button();
// This DataGridView control displays the contents of the list.
private DataGridView customersDataGridView = new DataGridView();
// This BindingSource binds the list to the DataGridView control.
private BindingSource customersBindingSource = new BindingSource();
public Form1()
{
InitializeComponent();
// Set up the "Change Item" button.
this.changeItemBtn.Text = "Change Item";
this.changeItemBtn.Dock = DockStyle.Bottom;
this.changeItemBtn.Click +=
new EventHandler(changeItemBtn_Click);
this.Controls.Add(this.changeItemBtn);
// Set up the DataGridView.
customersDataGridView.Dock = DockStyle.Top;
this.Controls.Add(customersDataGridView);
this.Size = new Size(400, 200);
}
private void Form1_Load(object sender, EventArgs e)
{
// Create and populate the list of DemoCustomer objects
// which will supply data to the DataGridView.
BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
customerList.Add(DemoCustomer.CreateNewCustomer());
// Bind the list to the BindingSource.
this.customersBindingSource.DataSource = customerList;
// Attach the BindingSource to the DataGridView.
this.customersDataGridView.DataSource =
this.customersBindingSource;
}
// Change the value of the CompanyName property for the first
// item in the list when the "Change Item" button is clicked.
void changeItemBtn_Click(object sender, EventArgs e)
{
// Get a reference to the list from the BindingSource.
BindingList<DemoCustomer> customerList =
this.customersBindingSource.DataSource as BindingList<DemoCustomer>;
// Change the value of the CompanyName property for the
// first item in the list.
customerList[0].CustomerName = "Tailspin Toys";
customerList[0].PhoneNumber = "(708)555-0150";
}
}
// This is a simple customer class that
// implements the IPropertyChange interface.
public class DemoCustomer : INotifyPropertyChanged
{
// These fields hold the values for the public properties.
private Guid idValue = Guid.NewGuid();
private string customerNameValue = String.Empty;
private string phoneNumberValue = String.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// This method is called by the Set accessor of each property.
// The CallerMemberName attribute that is applied to the optional propertyName
// parameter causes the property name of the caller to be substituted as an argument.
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// The constructor is private to enforce the factory pattern.
private DemoCustomer()
{
customerNameValue = "Customer";
phoneNumberValue = "(312)555-0100";
}
// This is the public factory method.
public static DemoCustomer CreateNewCustomer()
{
return new DemoCustomer();
}
// This property represents an ID, suitable
// for use as a primary key in a database.
public Guid ID
{
get
{
return this.idValue;
}
}
public string CustomerName
{
get
{
return this.customerNameValue;
}
set
{
if (value != this.customerNameValue)
{
this.customerNameValue = value;
NotifyPropertyChanged();
}
}
}
public string PhoneNumber
{
get
{
return this.phoneNumberValue;
}
set
{
if (value != this.phoneNumberValue)
{
this.phoneNumberValue = value;
NotifyPropertyChanged();
}
}
}
}
}

Resources