I have seen examples of using WPF + WCF to implement a Shared Whiteboard (codeproject: http://www.codeproject.com/KB/WCF/DrawMeWCF.aspx), recently nothing to create a winform wheel again. In that article, the inkcanvas Control + WCF duplex of WPF is used to submit a drawing from a client to the server. The server is responsible for notifying other clients.
Let's talk about my idea: Draw a picture on the server side. The client gets the drawing information on the server side through timer polling and redraws the picture on its own form, making the client look like it is drawing a picture synchronously with the server side. First, click server on the left and client on the right.
Of course, there is a disadvantage, that is, it can only be viewed on the drawing client of the server.
The project is very simple. The form + WCF library, the form is shared by the server and the client, while the login program distinguishes between the server and the client:
Public partial class login: Form <br/>{< br/> Public login () <br/>{< br/> initializecomponent (); <br/>}< br/> private void btnlogin_click (Object sender, eventargs e) <br/>{< br/> main = application. openforms ["Main"] as main; <br/> If (radserver. checked) <br/>{< br/> main. isserver = true; <br/> This. close (); <br/>}< br/> else if (radclient. checked) <br/>{< br/> main. isserver = false; <br/> This. close (); <br/>}< br/> else <br/> MessageBox. show (this, "Please select the type. "); <br/>}< br/> private void btncancel_click (Object sender, eventargs e) <br/>{< br/> application. exit (); <br/>}< br/>}
When the server is started, the WCF Service host is sent to the current Windows form application. For winform host, you must encapsulate servicehost in the thread to avoid UI blocking. (See my other blog: wcf faq (2) -- winform host UI blocking)
Text + = "[server]"; <br/> host = new threadedservicehost (typeof (wcfsharedrawlib. drawservice); <br/> host. open ();
When the client is started, create a remote proxy through the WCF data contract (interface. config is also shared. Therefore, the configurationmanager is used to read the service address in the servicemodel configuration, avoiding hard-coding and writing dead addresses.
# Region get info from config <br/> var conf = configurationmanager. openexeconfiguration (assembly. getentryassembly (). location); <br/> var svcconf = (servicemodelsectiongroup) Conf. getsectiongroup ("system. servicemodel "); <br/> var address =" "; <br/> foreach (serviceelement SVC in svcconf. services. services) <br/> address = SVC. host. baseaddresses [0]. baseaddress; <br/> # endregion <br/> var binding = new nettcpbinding (); <br/> binding. closetimeout = new timespan (0, 10, 0); <br/> binding. receivetimeout = new timespan (0, 10, 0); <br/> binding. sendtimeout = new timespan (0, 10, 0); <br/> var factory = new channelfactory <idrawservice> (binding, address); <br/> factory. open (); <br/> client = factory. createchannel ();
Complete mainform code:
Public partial class main: Form <br/>{< br/> public main () <br/>{< br/> initializecomponent (); <br/>}< br/> private drawctrl _ draw; <br/> Public bool isserver {Get; Set ;}< br/> Public threadedservicehost host {Get; set ;} <br/> Public idrawservice client {Get; Set ;}< br/> private void form1_load (Object sender, eventargs E) <br/>{< br/> _ draw = new drawctrl (); <br/> This. setstyle (controlsty Les. optimizeddoublebuffer | <br/> controlstyles. resizeredraw | <br/> controlstyles. allpaintinginwmpaint, true); <br/>}< br/> private void main_shown (Object sender, eventargs E) <br/>{< br/> var login = New Login (); <br/> login. startposition = formstartposition. centerparent; <br/> login. showdialog (this); <br/> If (isserver) <br/> setserver (); <br/> else <br/> setclient (); <br/>}< br/> private Vo Id main_formclosing (Object sender, formclosingeventargs e) <br/>{< br/> timer1.stop (); <br/> If (client! = NULL) <br/> (idisposable) client). Dispose (); <br/> If (host! = NULL) <br/> (idisposable) host ). dispose (); <br/> _ draw. dispose (); <br/>}< br/> private void setserver () <br/>{< br/> text + = "[server]"; <br/> host = new threadedservicehost (typeof (wcfsharedrawlib. drawservice); <br/> host. open (); <br/> drawservice. onrequestdata + = () => {return _ draw. lines ;}; <br/> panel1.paint + = (S, EVT) =>{ _ draw. drawall (EVT. graphics) ;}; <br/> panel1.mousedown + = (S, EVT) =>{ _ draw. begin () ;}; <br/> panel1.mousemove + = (S, EVT) =>{ _ draw. draw (panel1.creategraphics (), EVT. location) ;}; <br/> panel1.mouseup + = (S, EVT) =>{ _ draw. drawtemp (panel1.creategraphics (); _ draw. stop () ;}; <br/> btnredraw. click + = (S, EVT) =>{ _ draw. clear (panel1.creategraphics (), panel1.backcolor) ;}; <br/>}< br/> private void setclient () <br/> {<br/> text + = "[client]"; <br/> # region get info from config <br/> var conf = configurationmanager. openexeconfiguration (assembly. getentryassembly (). location); <br/> var svcconf = (servicemodelsectiongroup) Conf. getsectiongroup ("system. servicemodel "); <br/> var address =" "; <br/> foreach (serviceelement SVC in svcconf. services. services) <br/> address = SVC. host. baseaddresses [0]. baseaddress; <br/> # endregion <br/> var binding = new nettcpbinding (); <br/> binding. closetimeout = new timespan (0, 10, 0); <br/> binding. receivetimeout = new timespan (0, 10, 0); <br/> binding. sendtimeout = new timespan (0, 10, 0); <br/> var factory = new channelfactory <idrawservice> (binding, address); <br/> factory. open (); <br/> client = factory. createchannel (); <br/> timer1.interval = 1000; <br/> timer1.start (); <br/> timer1.tick + = (S, EVT) ==>< br/>{< br/> try <br/>{< br/> var lines = client. getdrawdata (); <br/> If (lines. count> 1) <br/> _ draw. drawall (panel1.creategraphics (), lines); <br/> else <br/> _ draw. clear (panel1.creategraphics (), panel1.backcolor); <br/>}< br/> catch (exception) <br/>{}< br/> }; <br/>}< br/>}
You can see that the client starts a timer and regularly asks for drawing information from the service:
VaR lines = client. getdrawdata ();
When the getdrawdata () method of the server is called, a static event onrequestdata is triggered. The server UI registers the event processing method and returns the current image information. A simple process is as follows:
Code of the WCF Service:
[Servicebehavior (instancecontextmode = instancecontextmode. single, <br/> concurrencymode = concurrencymode. multiple)] <br/> public class drawservice: idrawservice <br/> {<br/> Public static event func <list <point> onrequestdata; <br/> public list <point> getdrawdata () <br/>{< br/> return onrequestdata (); <br/>}< br/>}
Of course, we also need a control class for drawing:
(In the above UI code, the setservice method calls the following method in each UI event)
1. Start when the mouse is pressed
2. When the mouse moves, record the coordinates and draw the current line
3. When the mouse is released, it will end (re-draw the current line, otherwise there will be a breakpoint that looks uneven)
4. redraw all lines in the paint event
Public class drawctrl: idisposable <br/>{< br/> private list <point> _ lines; <br/> private list <point> _ templines; <br/> private list <point> _ newline; <br/> private bool _ isdrawing; <br/> private pen _ pen; <br/> private int _ redrawnum; <br/> public list <point> lines <br/> {<br/> Get <br/> {<br/> lock (this) <br/>{< br/> return _ lines; <br/>}< br/> Public drawct RL () <br/>{< br/> _ lines = new list <point> (); <br/> _ templines = new list <point> (); <br/> _ isdrawing = false; <br/> _ pen = new pen (brushes. black, 5); <br/>}< br/> Public void begin () <br/>{< br/> _ isdrawing = true; <br/> _ newline = new list <point> (); <br/> _ templines. clear (); <br/> _ lines. add (_ newline); <br/> _ templines. add (_ newline); <br/> _ redrawnum = 0; <br/>}< br/> Public void Stop () <br/>{< br/> _ isdrawing = false; <br/>}< br/> Public void draw (Graphics g, point P) <br/>{< br/> If (! _ Isdrawing) return; <br/> lock (this) <br/> {<br/> _ newline. add (p); <br/> var COUNT = _ newline. count; <br/> If (count> 1) <br/>{< br/> G. drawline (_ pen, _ newline [count-2], _ newline [count-1]); <br/> G. dispose (); <br/>}< br/> Public void clear (Graphics g, color) <br/>{< br/> lock (this) <br/>{< br/> _ lines. clear (); <br/>}< br/> G. clear (color); <br/>}< br/> Public void drawall (Graphics g) <br/>{< br/> drawall (G, _ lines ); <br/>}< br/> Public void drawtemp (Graphics g) <br/>{< br/> drawall (G, _ templines ); <br/>}< br/> Public void drawall (Graphics g, list <point> lines) <br/>{< br/> lock (this) <br/>{< br/> lines. foreach (line => {If (line. count> 1) g. drawlines (_ pen, line. toarray () ;}); <br/> G. dispose (); <br/>}< br/> Public void dispose () <br/>{< br/> _ lines. foreach (L => L. clear (); <br/> _ lines. clear (); <br/> _ pen. dispose (); <br/>}< br/>}
OK. A simple whiteboard sharing program is ready.