WCF,从服务中访问Windows窗体控件

5

我有一个托管在Windows窗体中的WCF服务。

如何从我的服务方法中访问窗体的控件?

例如,我有:

public interface IService    {
    [ServiceContract]
    string PrintMessage(string message);
}

public class Service: IService    
{
    public string PrintMessage(string message)
    {
        //How do I access the forms controls from here?
        FormTextBox.Text = message;
    }
}
5个回答

5

首先,ServiceContract属性应该放在接口上,而不是PrintMessage()方法上。

使用您示例的修正版本,可以这样做。

[ServiceContract]
public interface IService
{
    [OperationContract]
    string PrintMessage(string message);
}
public class Service : IService
{
    public string PrintMessage(string message)
    {
        // Invoke the delegate here.
        try {
            UpdateTextDelegate handler = TextUpdater;
            if (handler != null)
            {
                handler(this, new UpdateTextEventArgs(message));
            }
        } catch {
        }
    }
    public static UpdateTextDelegate TextUpdater { get; set; }
}

public delegate void UpdateTextDelegate(object sender, UpdateTextEventArgs e);

public class UpdateTextEventArgs
{
    public string Text { get; set; }
    public UpdateTextEventArgs(string text)
    {
        Text = text;
    }
}

public class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();

        // Update the delegate of your service here.
        Service.TextUpdater = ShowMessageBox;

        // Create your WCF service here
        ServiceHost myService = new ServiceHost(typeof(IService), uri);
    }
    // The ShowMessageBox() method has to match the signature of
    // the UpdateTextDelegate delegate.
    public void ShowMessageBox(object sender, UpdateTextEventArgs e)
    {
        // Use Invoke() to make sure the UI interaction happens
        // on the UI thread...just in case this delegate is
        // invoked on another thread.
        Invoke((MethodInvoker) delegate {
            MessageBox.Show(e.Text);
        } );
    }
}

这基本上是@Simon Fox建议的解决方案,即使用委托。这只是为了更好地阐述这个方案。


那看起来很不错,马特,除了这一行代码:“service.TextUpdater = ShowMessageBox;”当我创建我的服务时,我会这样做: ServiceHost myService = new ServiceHost(typeof(IService), uri); 那么如果TextUpdater属性是ServiceHost类型而不是Service类型,我该如何设置它呢? - Tom_123
好的。将TextUpdater属性设置为Service类的静态属性。这样,当服务被实例化时,它就可以访问委托。 - Matt Davis
你还可以更进一步,使用WeakReference。不过这并不是一件简单的事情。 - Anderson Imes
当然。如果静态变量引用了一个对象,当该对象不再使用时(比如你关闭了窗体,但这不是主要窗体),那么仍然有东西引用它。在这种情况下,唯一会导致该对象的引用计数降为零的事情就是应用程序域关闭(应用程序关闭)。你可以通过上述三种方法之一来避免这种情况。垃圾回收器并不能总是拯救你。这是一个艰苦的教训,但这就是为什么像Ants Memory Profiler这样的工具存在的原因。 - Anderson Imes
当然。这篇文章看起来非常全面。它介绍了几种方法。http://www.codeproject.com/KB/cs/WeakEvents.aspx。如果您拥有该方法的窗体的生命周期与应用程序相同,我不会这样做,但如果这是一个您将要打开多次的窗体,则在您的窗体关闭时一定需要停止监听事件。 - Anderson Imes
显示剩余4条评论

3

处理这种情况的最佳方式是将表单作为依赖项注入到服务中。我会定义某种接口来将表单代码与WCF代码解耦:

public interface IFormService
{
    string Text { get; set; }
}

您可以通过设置要更新的实际属性,使您的表单实现IFormService接口。

您的服务需要一个IFormService的实例来完成其工作:

public class Service : IService
{
    private readonly IFormService form;

    public Service(IFormService form)
    {
        this.form = form
    }

    public string PrintMessage(string message)
    {
        this.form.Text = message;
    }
}

由于Service类现在没有默认构造函数,您还需要实现一个自定义的ServiceHostFactory,该工厂可以创建Service类的实例并注入IFormService的具体实现。


1
使用委托。在窗体的代码后台中创建一个引用写入文本框的方法的委托并将其传递给服务,当服务想要打印消息时,它可以调用委托。
您将遇到更新文本框的问题,因为委托将在与创建文本框不同的线程上调用。在 WPF 中,您可以使用 Dispatcher.BeginInvoke 来解决此问题,不确定 WinForms 中等价的是什么。

0

为了完整起见,还有一种使用相同解决方案的更简单的方法。

您可以使用invoke方法直接在同一线程上调用委托。

Invoke(new MethodInvoker(delegate{FormTextBox.Text = message;});

0
服务将需要一个对表单实例的引用,而不仅仅是表单的类型。
此外,您需要一种方法从服务类发送值到表单控件。这可以通过使控件本身公共或受保护来完成,或者可以在表单类上创建属性,以设置控件上的属性。
一个简单的方法如下:
  1. 向您的服务类添加一个构造函数,该构造函数需要对您的表单实例的引用。如果您正在表单内部托管您的服务,则这应该很容易。
  2. 为要从服务中设置的控件属性添加公共属性。这些属性应该是公共或内部的,并将设置控件属性的值。
  3. 在服务中设置新添加属性的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接