").append(x.parseHTML(e)).find(i):e)}).complete(r&&function(e,t){s.each(r,o||[e.responseText,t,e])}),this},x.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){x.fn[t]=function(e){return this.on(t,e)}}),x.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:yn,type:"GET",isLocal:Cn.test(mn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Dn,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":x.parseJSON,"text xml":x.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?_n(_n(e,x.ajaxSettings),t):_n(x.ajaxSettings,e)},ajaxPrefilter:Hn(An),ajaxTransport:Hn(jn),ajax:function(e,n){"object"==typeof e&&(n=e,e=t),n=n||{};var r,i,o,a,s,l,u,c,p=x.ajaxSetup({},n),f=p.context||p,d=p.context&&(f.nodeType||f.jquery)?x(f):x.event,h=x.Deferred(),g=x.Callbacks("once memory"),m=p.statusCode||{},y={},v={},b=0,w="canceled",C={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!c){c={};while(t=Tn.exec(a))c[t[1].toLowerCase()]=t[2]}t=c[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?a:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=v[n]=v[n]||e,y[e]=t),this},overrideMimeType:function(e){return b||(p.mimeType=e),this},statusCode:function(e){var t;if(e)if(2>b)for(t in e)m[t]=[m[t],e[t]];else C.always(e[C.status]);return this},abort:function(e){var t=e||w;return u&&u.abort(t),k(0,t),this}};if(h.promise(C).complete=g.add,C.success=C.done,C.error=C.fail,p.url=((e||p.url||yn)+"").replace(xn,"").replace(kn,mn[1]+"//"),p.type=n.method||n.type||p.method||p.type,p.dataTypes=x.trim(p.dataType||"*").toLowerCase().match(T)||[""],null==p.crossDomain&&(r=En.exec(p.url.toLowerCase()),p.crossDomain=!(!r||r[1]===mn[1]&&r[2]===mn[2]&&(r[3]||("http:"===r[1]?"80":"443"))===(mn[3]||("http:"===mn[1]?"80":"443")))),p.data&&p.processData&&"string"!=typeof p.data&&(p.data=x.param(p.data,p.traditional)),qn(An,p,n,C),2===b)return C;l=p.global,l&&0===x.active++&&x.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!Nn.test(p.type),o=p.url,p.hasContent||(p.data&&(o=p.url+=(bn.test(o)?"&":"?")+p.data,delete p.data),p.cache===!1&&(p.url=wn.test(o)?o.replace(wn,"$1_="+vn++):o+(bn.test(o)?"&":"?")+"_="+vn++)),p.ifModified&&(x.lastModified[o]&&C.setRequestHeader("If-Modified-Since",x.lastModified[o]),x.etag[o]&&C.setRequestHeader("If-None-Match",x.etag[o])),(p.data&&p.hasContent&&p.contentType!==!1||n.contentType)&&C.setRequestHeader("Content-Type",p.contentType),C.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Dn+"; q=0.01":""):p.accepts["*"]);for(i in p.headers)C.setRequestHeader(i,p.headers[i]);if(p.beforeSend&&(p.beforeSend.call(f,C,p)===!1||2===b))return C.abort();w="abort";for(i in{success:1,error:1,complete:1})C[i](p[i]);if(u=qn(jn,p,n,C)){C.readyState=1,l&&d.trigger("ajaxSend",[C,p]),p.async&&p.timeout>0&&(s=setTimeout(function(){C.abort("timeout")},p.timeout));try{b=1,u.send(y,k)}catch(N){if(!(2>b))throw N;k(-1,N)}}else k(-1,"No Transport");function k(e,n,r,i){var c,y,v,w,T,N=n;2!==b&&(b=2,s&&clearTimeout(s),u=t,a=i||"",C.readyState=e>0?4:0,c=e>=200&&300>e||304===e,r&&(w=Mn(p,C,r)),w=On(p,w,C,c),c?(p.ifModified&&(T=C.getResponseHeader("Last-Modified"),T&&(x.lastModified[o]=T),T=C.getResponseHeader("etag"),T&&(x.etag[o]=T)),204===e||"HEAD"===p.type?N="nocontent":304===e?N="notmodified":(N=w.state,y=w.data,v=w.error,c=!v)):(v=N,(e||!N)&&(N="error",0>e&&(e=0))),C.status=e,C.statusText=(n||N)+"",c?h.resolveWith(f,[y,N,C]):h.rejectWith(f,[C,N,v]),C.statusCode(m),m=t,l&&d.trigger(c?"ajaxSuccess":"ajaxError",[C,p,c?y:v]),g.fireWith(f,[C,N]),l&&(d.trigger("ajaxComplete",[C,p]),--x.active||x.event.trigger("ajaxStop")))}return C},getJSON:function(e,t,n){return x.get(e,t,n,"json")},getScript:function(e,n){return x.get(e,t,n,"script")}}),x.each(["get","post"],function(e,n){x[n]=function(e,r,i,o){return x.isFunction(r)&&(o=o||i,i=r,r=t),x.ajax({url:e,type:n,dataType:o,data:r,success:i})}});function Mn(e,n,r){var i,o,a,s,l=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),o===t&&(o=e.mimeType||n.getResponseHeader("Content-Type"));if(o)for(s in l)if(l[s]&&l[s].test(o)){u.unshift(s);break}if(u[0]in r)a=u[0];else{for(s in r){if(!u[0]||e.converters[s+" "+u[0]]){a=s;break}i||(i=s)}a=a||i}return a?(a!==u[0]&&u.unshift(a),r[a]):t}function On(e,t,n,r){var i,o,a,s,l,u={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)u[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!l&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),l=o,o=c.shift())if("*"===o)o=l;else if("*"!==l&&l!==o){if(a=u[l+" "+o]||u["* "+o],!a)for(i in u)if(s=i.split(" "),s[1]===o&&(a=u[l+" "+s[0]]||u["* "+s[0]])){a===!0?a=u[i]:u[i]!==!0&&(o=s[0],c.unshift(s[1]));break}if(a!==!0)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(p){return{state:"parsererror",error:a?p:"No conversion from "+l+" to "+o}}}return{state:"success",data:t}}x.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/(?:java|ecma)script/},converters:{"text script":function(e){return x.globalEval(e),e}}}),x.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),x.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=a.head||x("head")[0]||a.documentElement;return{send:function(t,i){n=a.createElement("script"),n.async=!0,e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,t){(t||!n.readyState||/loaded|complete/.test(n.readyState))&&(n.onload=n.onreadystatechange=null,n.parentNode&&n.parentNode.removeChild(n),n=null,t||i(200,"success"))},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(t,!0)}}}});var Fn=[],Bn=/(=)\?(?=&|$)|\?\?/;x.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Fn.pop()||x.expando+"_"+vn++;return this[e]=!0,e}}),x.ajaxPrefilter("json jsonp",function(n,r,i){var o,a,s,l=n.jsonp!==!1&&(Bn.test(n.url)?"url":"string"==typeof n.data&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Bn.test(n.data)&&"data");return l||"jsonp"===n.dataTypes[0]?(o=n.jsonpCallback=x.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,l?n[l]=n[l].replace(Bn,"$1"+o):n.jsonp!==!1&&(n.url+=(bn.test(n.url)?"&":"?")+n.jsonp+"="+o),n.converters["script json"]=function(){return s||x.error(o+" was not called"),s[0]},n.dataTypes[0]="json",a=e[o],e[o]=function(){s=arguments},i.always(function(){e[o]=a,n[o]&&(n.jsonpCallback=r.jsonpCallback,Fn.push(o)),s&&x.isFunction(a)&&a(s[0]),s=a=t}),"script"):t});var Pn,Rn,Wn=0,$n=e.ActiveXObject&&function(){var e;for(e in Pn)Pn[e](t,!0)};function In(){try{return new e.XMLHttpRequest}catch(t){}}function zn(){try{return new e.ActiveXObject("Microsoft.XMLHTTP")}catch(t){}}x.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&In()||zn()}:In,Rn=x.ajaxSettings.xhr(),x.support.cors=!!Rn&&"withCredentials"in Rn,Rn=x.support.ajax=!!Rn,Rn&&x.ajaxTransport(function(n){if(!n.crossDomain||x.support.cors){var r;return{send:function(i,o){var a,s,l=n.xhr();if(n.username?l.open(n.type,n.url,n.async,n.username,n.password):l.open(n.type,n.url,n.async),n.xhrFields)for(s in n.xhrFields)l[s]=n.xhrFields[s];n.mimeType&&l.overrideMimeType&&l.overrideMimeType(n.mimeType),n.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest");try{for(s in i)l.setRequestHeader(s,i[s])}catch(u){}l.send(n.hasContent&&n.data||null),r=function(e,i){var s,u,c,p;try{if(r&&(i||4===l.readyState))if(r=t,a&&(l.onreadystatechange=x.noop,$n&&delete Pn[a]),i)4!==l.readyState&&l.abort();else{p={},s=l.status,u=l.getAllResponseHeaders(),"string"==typeof l.responseText&&(p.text=l.responseText);try{c=l.statusText}catch(f){c=""}s||!n.isLocal||n.crossDomain?1223===s&&(s=204):s=p.text?200:404}}catch(d){i||o(-1,d)}p&&o(s,c,p,u)},n.async?4===l.readyState?setTimeout(r):(a=++Wn,$n&&(Pn||(Pn={},x(e).unload($n)),Pn[a]=r),l.onreadystatechange=r):r()},abort:function(){r&&r(t,!0)}}}});var Xn,Un,Vn=/^(?:toggle|show|hide)$/,Yn=RegExp("^(?:([+-])=|)("+w+")([a-z%]*)$","i"),Jn=/queueHooks$/,Gn=[nr],Qn={"*":[function(e,t){var n=this.createTween(e,t),r=n.cur(),i=Yn.exec(t),o=i&&i[3]||(x.cssNumber[e]?"":"px"),a=(x.cssNumber[e]||"px"!==o&&+r)&&Yn.exec(x.css(n.elem,e)),s=1,l=20;if(a&&a[3]!==o){o=o||a[3],i=i||[],a=+r||1;do s=s||".5",a/=s,x.style(n.elem,e,a+o);while(s!==(s=n.cur()/r)&&1!==s&&--l)}return i&&(a=n.start=+a||+r||0,n.unit=o,n.end=i[1]?a+(i[1]+1)*i[2]:+i[2]),n}]};function Kn(){return setTimeout(function(){Xn=t}),Xn=x.now()}function Zn(e,t,n){var r,i=(Qn[t]||[]).concat(Qn["*"]),o=0,a=i.length;for(;a>o;o++)if(r=i[o].call(n,t,e))return r}function er(e,t,n){var r,i,o=0,a=Gn.length,s=x.Deferred().always(function(){delete l.elem}),l=function(){if(i)return!1;var t=Xn||Kn(),n=Math.max(0,u.startTime+u.duration-t),r=n/u.duration||0,o=1-r,a=0,l=u.tweens.length;for(;l>a;a++)u.tweens[a].run(o);return s.notifyWith(e,[u,o,n]),1>o&&l?n:(s.resolveWith(e,[u]),!1)},u=s.promise({elem:e,props:x.extend({},t),opts:x.extend(!0,{specialEasing:{}},n),originalProperties:t,originalOptions:n,startTime:Xn||Kn(),duration:n.duration,tweens:[],createTween:function(t,n){var r=x.Tween(e,u.opts,t,n,u.opts.specialEasing[t]||u.opts.easing);return u.tweens.push(r),r},stop:function(t){var n=0,r=t?u.tweens.length:0;if(i)return this;for(i=!0;r>n;n++)u.tweens[n].run(1);return t?s.resolveWith(e,[u,t]):s.rejectWith(e,[u,t]),this}}),c=u.props;for(tr(c,u.opts.specialEasing);a>o;o++)if(r=Gn[o].call(u,e,c,u.opts))return r;return x.map(c,Zn,u),x.isFunction(u.opts.start)&&u.opts.start.call(e,u),x.fx.timer(x.extend(l,{elem:e,anim:u,queue:u.opts.queue})),u.progress(u.opts.progress).done(u.opts.done,u.opts.complete).fail(u.opts.fail).always(u.opts.always)}function tr(e,t){var n,r,i,o,a;for(n in e)if(r=x.camelCase(n),i=t[r],o=e[n],x.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),a=x.cssHooks[r],a&&"expand"in a){o=a.expand(o),delete e[r];for(n in o)n in e||(e[n]=o[n],t[n]=i)}else t[r]=i}x.Animation=x.extend(er,{tweener:function(e,t){x.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;i>r;r++)n=e[r],Qn[n]=Qn[n]||[],Qn[n].unshift(t)},prefilter:function(e,t){t?Gn.unshift(e):Gn.push(e)}});function nr(e,t,n){var r,i,o,a,s,l,u=this,c={},p=e.style,f=e.nodeType&&nn(e),d=x._data(e,"fxshow");n.queue||(s=x._queueHooks(e,"fx"),null==s.unqueued&&(s.unqueued=0,l=s.empty.fire,s.empty.fire=function(){s.unqueued||l()}),s.unqueued++,u.always(function(){u.always(function(){s.unqueued--,x.queue(e,"fx").length||s.empty.fire()})})),1===e.nodeType&&("height"in t||"width"in t)&&(n.overflow=[p.overflow,p.overflowX,p.overflowY],"inline"===x.css(e,"display")&&"none"===x.css(e,"float")&&(x.support.inlineBlockNeedsLayout&&"inline"!==ln(e.nodeName)?p.zoom=1:p.display="inline-block")),n.overflow&&(p.overflow="hidden",x.support.shrinkWrapBlocks||u.always(function(){p.overflow=n.overflow[0],p.overflowX=n.overflow[1],p.overflowY=n.overflow[2]}));for(r in t)if(i=t[r],Vn.exec(i)){if(delete t[r],o=o||"toggle"===i,i===(f?"hide":"show"))continue;c[r]=d&&d[r]||x.style(e,r)}if(!x.isEmptyObject(c)){d?"hidden"in d&&(f=d.hidden):d=x._data(e,"fxshow",{}),o&&(d.hidden=!f),f?x(e).show():u.done(function(){x(e).hide()}),u.done(function(){var t;x._removeData(e,"fxshow");for(t in c)x.style(e,t,c[t])});for(r in c)a=Zn(f?d[r]:0,r,u),r in d||(d[r]=a.start,f&&(a.end=a.start,a.start="width"===r||"height"===r?1:0))}}function rr(e,t,n,r,i){return new rr.prototype.init(e,t,n,r,i)}x.Tween=rr,rr.prototype={constructor:rr,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||"swing",this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(x.cssNumber[n]?"":"px")},cur:function(){var e=rr.propHooks[this.prop];return e&&e.get?e.get(this):rr.propHooks._default.get(this)},run:function(e){var t,n=rr.propHooks[this.prop];return this.pos=t=this.options.duration?x.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):rr.propHooks._default.set(this),this}},rr.prototype.init.prototype=rr.prototype,rr.propHooks={_default:{get:function(e){var t;return null==e.elem[e.prop]||e.elem.style&&null!=e.elem.style[e.prop]?(t=x.css(e.elem,e.prop,""),t&&"auto"!==t?t:0):e.elem[e.prop]},set:function(e){x.fx.step[e.prop]?x.fx.step[e.prop](e):e.elem.style&&(null!=e.elem.style[x.cssProps[e.prop]]||x.cssHooks[e.prop])?x.style(e.elem,e.prop,e.now+e.unit):e.elem[e.prop]=e.now}}},rr.propHooks.scrollTop=rr.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},x.each(["toggle","show","hide"],function(e,t){var n=x.fn[t];x.fn[t]=function(e,r,i){return null==e||"boolean"==typeof e?n.apply(this,arguments):this.animate(ir(t,!0),e,r,i)}}),x.fn.extend({fadeTo:function(e,t,n,r){return this.filter(nn).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(e,t,n,r){var i=x.isEmptyObject(e),o=x.speed(t,n,r),a=function(){var t=er(this,x.extend({},e),o);(i||x._data(this,"finish"))&&t.stop(!0)};return a.finish=a,i||o.queue===!1?this.each(a):this.queue(o.queue,a)},stop:function(e,n,r){var i=function(e){var t=e.stop;delete e.stop,t(r)};return"string"!=typeof e&&(r=n,n=e,e=t),n&&e!==!1&&this.queue(e||"fx",[]),this.each(function(){var t=!0,n=null!=e&&e+"queueHooks",o=x.timers,a=x._data(this);if(n)a[n]&&a[n].stop&&i(a[n]);else for(n in a)a[n]&&a[n].stop&&Jn.test(n)&&i(a[n]);for(n=o.length;n--;)o[n].elem!==this||null!=e&&o[n].queue!==e||(o[n].anim.stop(r),t=!1,o.splice(n,1));(t||!r)&&x.dequeue(this,e)})},finish:function(e){return e!==!1&&(e=e||"fx"),this.each(function(){var t,n=x._data(this),r=n[e+"queue"],i=n[e+"queueHooks"],o=x.timers,a=r?r.length:0;for(n.finish=!0,x.queue(this,e,[]),i&&i.stop&&i.stop.call(this,!0),t=o.length;t--;)o[t].elem===this&&o[t].queue===e&&(o[t].anim.stop(!0),o.splice(t,1));for(t=0;a>t;t++)r[t]&&r[t].finish&&r[t].finish.call(this);delete n.finish})}});function ir(e,t){var n,r={height:e},i=0;for(t=t?1:0;4>i;i+=2-t)n=Zt[i],r["margin"+n]=r["padding"+n]=e;return t&&(r.opacity=r.width=e),r}x.each({slideDown:ir("show"),slideUp:ir("hide"),slideToggle:ir("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,t){x.fn[e]=function(e,n,r){return this.animate(t,e,n,r)}}),x.speed=function(e,t,n){var r=e&&"object"==typeof e?x.extend({},e):{complete:n||!n&&t||x.isFunction(e)&&e,duration:e,easing:n&&t||t&&!x.isFunction(t)&&t};return r.duration=x.fx.off?0:"number"==typeof r.duration?r.duration:r.duration in x.fx.speeds?x.fx.speeds[r.duration]:x.fx.speeds._default,(null==r.queue||r.queue===!0)&&(r.queue="fx"),r.old=r.complete,r.complete=function(){x.isFunction(r.old)&&r.old.call(this),r.queue&&x.dequeue(this,r.queue)},r},x.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2}},x.timers=[],x.fx=rr.prototype.init,x.fx.tick=function(){var e,n=x.timers,r=0;for(Xn=x.now();n.length>r;r++)e=n[r],e()||n[r]!==e||n.splice(r--,1);n.length||x.fx.stop(),Xn=t},x.fx.timer=function(e){e()&&x.timers.push(e)&&x.fx.start()},x.fx.interval=13,x.fx.start=function(){Un||(Un=setInterval(x.fx.tick,x.fx.interval))},x.fx.stop=function(){clearInterval(Un),Un=null},x.fx.speeds={slow:600,fast:200,_default:400},x.fx.step={},x.expr&&x.expr.filters&&(x.expr.filters.animated=function(e){return x.grep(x.timers,function(t){return e===t.elem}).length}),x.fn.offset=function(e){if(arguments.length)return e===t?this:this.each(function(t){x.offset.setOffset(this,e,t)});var n,r,o={top:0,left:0},a=this[0],s=a&&a.ownerDocument;if(s)return n=s.documentElement,x.contains(n,a)?(typeof a.getBoundingClientRect!==i&&(o=a.getBoundingClientRect()),r=or(s),{top:o.top+(r.pageYOffset||n.scrollTop)-(n.clientTop||0),left:o.left+(r.pageXOffset||n.scrollLeft)-(n.clientLeft||0)}):o},x.offset={setOffset:function(e,t,n){var r=x.css(e,"position");"static"===r&&(e.style.position="relative");var i=x(e),o=i.offset(),a=x.css(e,"top"),s=x.css(e,"left"),l=("absolute"===r||"fixed"===r)&&x.inArray("auto",[a,s])>-1,u={},c={},p,f;l?(c=i.position(),p=c.top,f=c.left):(p=parseFloat(a)||0,f=parseFloat(s)||0),x.isFunction(t)&&(t=t.call(e,n,o)),null!=t.top&&(u.top=t.top-o.top+p),null!=t.left&&(u.left=t.left-o.left+f),"using"in t?t.using.call(e,u):i.css(u)}},x.fn.extend({position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===x.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),x.nodeName(e[0],"html")||(n=e.offset()),n.top+=x.css(e[0],"borderTopWidth",!0),n.left+=x.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-x.css(r,"marginTop",!0),left:t.left-n.left-x.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||s;while(e&&!x.nodeName(e,"html")&&"static"===x.css(e,"position"))e=e.offsetParent;return e||s})}}),x.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);x.fn[e]=function(i){return x.access(this,function(e,i,o){var a=or(e);return o===t?a?n in a?a[n]:a.document.documentElement[i]:e[i]:(a?a.scrollTo(r?x(a).scrollLeft():o,r?o:x(a).scrollTop()):e[i]=o,t)},e,i,arguments.length,null)}});function or(e){return x.isWindow(e)?e:9===e.nodeType?e.defaultView||e.parentWindow:!1}x.each({Height:"height",Width:"width"},function(e,n){x.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){x.fn[i]=function(i,o){var a=arguments.length&&(r||"boolean"!=typeof i),s=r||(i===!0||o===!0?"margin":"border");return x.access(this,function(n,r,i){var o;return x.isWindow(n)?n.document.documentElement["client"+e]:9===n.nodeType?(o=n.documentElement,Math.max(n.body["scroll"+e],o["scroll"+e],n.body["offset"+e],o["offset"+e],o["client"+e])):i===t?x.css(n,r,s):x.style(n,r,i,s)},n,a?i:t,a,null)}})}),x.fn.size=function(){return this.length},x.fn.andSelf=x.fn.addBack,"object"==typeof module&&module&&"object"==typeof module.exports?module.exports=x:(e.jQuery=e.$=x,"function"==typeof define&&define.amd&&define("jquery",[],function(){return x}))})(window);
diff --git a/Document/line.png b/Document/line.png
new file mode 100755
index 0000000..6722a55
Binary files /dev/null and b/Document/line.png differ
diff --git a/Document/mark.png b/Document/mark.png
new file mode 100755
index 0000000..3932a2c
Binary files /dev/null and b/Document/mark.png differ
diff --git a/Document/node.png b/Document/node.png
new file mode 100755
index 0000000..03b0706
Binary files /dev/null and b/Document/node.png differ
diff --git a/Document/out.png b/Document/out.png
new file mode 100755
index 0000000..47f19c4
Binary files /dev/null and b/Document/out.png differ
diff --git a/Document/result.png b/Document/result.png
new file mode 100755
index 0000000..30e4fb7
Binary files /dev/null and b/Document/result.png differ
diff --git a/ST.Library.UI/Properties/AssemblyInfo.cs b/ST.Library.UI/Properties/AssemblyInfo.cs
new file mode 100755
index 0000000..caa7f3a
--- /dev/null
+++ b/ST.Library.UI/Properties/AssemblyInfo.cs
@@ -0,0 +1,38 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Resources;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ST.Library.UI")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("None")]
+[assembly: AssemblyProduct("ST.Library.UI")]
+[assembly: AssemblyCopyright("Copyright © Crystal_lz")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("5a7c4557-5eb1-435a-84d2-49cd67d1ac10")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
+[assembly: NeutralResourcesLanguageAttribute("zh-Hans")]
diff --git a/ST.Library.UI/ST.Library.UI.csproj b/ST.Library.UI/ST.Library.UI.csproj
new file mode 100755
index 0000000..e915560
--- /dev/null
+++ b/ST.Library.UI/ST.Library.UI.csproj
@@ -0,0 +1,64 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {EFFCC270-4999-4077-A543-56CCCCE92147}
+ Library
+ Properties
+ ST.Library.UI
+ ST.Library.UI
+ v3.5
+ 512
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ none
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+ bin\Release\ST.Library.UI.XML
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Component
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ST.Library.UI/STNodeEditor/STNode.cs b/ST.Library.UI/STNodeEditor/STNode.cs
new file mode 100755
index 0000000..a6c4e1f
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNode.cs
@@ -0,0 +1,874 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using System.Drawing;
+using System.Windows.Forms;
+using System.Collections;
+
+namespace ST.Library.UI
+{
+ public abstract class STNode
+ {
+ private STNodeEditor _Owner;
+ ///
+ /// 获取当前 Node 所有者
+ ///
+ public STNodeEditor Owner {
+ get { return _Owner; }
+ internal set {
+ if (value == _Owner) return;
+ if (_Owner != null) {
+ foreach (STNodeOption op in this._InputOptions) op.DisConnectionAll();
+ foreach (STNodeOption op in this._OutputOptions) op.DisConnectionAll();
+ }
+ _Owner = value;
+ this.OnOwnerChanged();
+ }
+ }
+
+ private bool _IsSelected;
+ ///
+ /// 获取或设置 Node 是否处于被选中状态
+ ///
+ public bool IsSelected {
+ get { return _IsSelected; }
+ set {
+ if (value == _IsSelected) return;
+ _IsSelected = value;
+ this.Invalidate();
+ this.OnSelectedChanged();
+ }
+ }
+
+ private bool _IsActive;
+ ///
+ /// 获取 Node 是否处于活动状态
+ ///
+ public bool IsActive {
+ get { return _IsActive; }
+ internal set {
+ if (value == _IsActive) return;
+ _IsActive = value;
+ this.OnActiveChanged();
+ }
+ }
+
+ private Color _TitleColor;
+ ///
+ /// 获取或设置标题背景颜色
+ ///
+ public Color TitleColor {
+ get { return _TitleColor; }
+ protected set {
+ _TitleColor = value;
+ this.Invalidate(this.TitleRectangle);
+ }
+ }
+
+ private Color _MarkColor;
+ ///
+ /// 获取或设置标记信息背景颜色
+ ///
+ public Color MarkColor {
+ get { return _MarkColor; }
+ protected set {
+ _MarkColor = value;
+ this.Invalidate(this._MarkRectangle);
+ }
+ }
+
+ private Color _ForeColor = Color.White;
+ ///
+ /// 获取或设置当前 Node 前景色
+ ///
+ public Color ForeColor {
+ get { return _ForeColor; }
+ protected set {
+ _ForeColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _BackColor;
+ ///
+ /// 获取或设置当前 Node 背景色
+ ///
+ public Color BackColor {
+ get { return _BackColor; }
+ protected set {
+ _BackColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private string _Title;
+ ///
+ /// 获取或设置 Node 标题
+ ///
+ public string Title {
+ get { return _Title; }
+ protected set {
+ _Title = value;
+ this.Invalidate(this.TitleRectangle);
+ }
+ }
+
+ private string _Mark;
+ ///
+ /// 获取或设置 Node 标记信息
+ ///
+ public string Mark {
+ get { return _Mark; }
+ set {
+ _Mark = value;
+ if (value == null)
+ _MarkLines = null;
+ else
+ _MarkLines = (from s in value.Split('\n') select s.Trim()).ToArray();
+ this.BuildSize(false, true, true);
+ //if (this._Owner != null) this._Owner.Invalidate();
+ //this.Invalidate();
+ //this.Invalidate(this._MarkRectangle);
+ }
+ }
+
+ private string[] _MarkLines;//单独存放行数据 不用每次在绘制中去拆分
+ ///
+ /// 获取 Node 标记信息行数据
+ ///
+ public string[] MarkLines {
+ get { return _MarkLines; }
+ }
+
+ private int _Left;
+ ///
+ /// 获取或设置 Node 左边坐标
+ ///
+ public int Left {
+ get { return _Left; }
+ set {
+ if (this._LockLocation) return;
+ _Left = value;
+ this.BuildSize(false, true, false);
+ //this._MarkRectangle = this.OnBuildMarkRectangle();
+ if (this._Owner != null) {
+ this._Owner.BuildLinePath();
+ this._Owner.BuildBounds();
+ }
+ this.OnMove(new EventArgs());
+ }
+ }
+
+ private int _Top;
+ ///
+ /// 获取或设置 Node 上边坐标
+ ///
+ public int Top {
+ get { return _Top; }
+ set {
+ if (this._LockLocation) return;
+ _Top = value;
+ this.BuildSize(false, true, false);
+ //this._MarkRectangle = this.OnBuildMarkRectangle();
+ if (this._Owner != null) {
+ this._Owner.BuildLinePath();
+ this._Owner.BuildBounds();
+ }
+ this.OnMove(new EventArgs());
+ }
+ }
+
+ private int _Width;
+ ///
+ /// 获取或设置 Node 宽度
+ ///
+ public int Width {
+ get { return _Width; }
+ }
+
+ private int _Height;
+ ///
+ /// 获取或设置 Node 高度
+ ///
+ public int Height {
+ get { return _Height; }
+ }
+ ///
+ /// 获取 Node 右边边坐标
+ ///
+ public int Right {
+ get { return _Left + _Width; }
+ }
+ ///
+ /// 获取 Node 下边坐标
+ ///
+ public int Bottom {
+ get { return _Top + _Height; }
+ }
+ ///
+ /// 获取 Node 矩形区域
+ ///
+ public Rectangle Rectangle {
+ get {
+ return new Rectangle(this._Left, this._Top, this._Width, this._Height);
+ }
+ }
+ ///
+ /// 获取 Node 标题矩形区域
+ ///
+ public Rectangle TitleRectangle {
+ get {
+ return new Rectangle(this._Left, this._Top, this._Width, this._TitleHeight);
+ }
+ }
+
+ private Rectangle _MarkRectangle;
+ ///
+ /// 获取 Node 标记矩形区域
+ ///
+ public Rectangle MarkRectangle {
+ get { return _MarkRectangle; }
+ }
+
+ private int _TitleHeight = 20;
+ ///
+ /// 获取或设置 Node 标题高度
+ ///
+ public int TitleHeight {
+ get { return _TitleHeight; }
+ protected set { _TitleHeight = value; }
+ }
+
+ private STNodeOptionCollection _InputOptions;
+ ///
+ /// 获取输入选项集合
+ ///
+ protected internal STNodeOptionCollection InputOptions {
+ get { return _InputOptions; }
+ }
+ ///
+ /// 获取输入选项集合个数
+ ///
+ public int InputOptionsCount { get { return _InputOptions.Count; } }
+
+ private STNodeOptionCollection _OutputOptions;
+ ///
+ /// 获取输出选项
+ ///
+ protected internal STNodeOptionCollection OutputOptions {
+ get { return _OutputOptions; }
+ }
+ ///
+ /// 获取输出选项个数
+ ///
+ public int OutputOptionsCount { get { return _OutputOptions.Count; } }
+
+ private STNodeControlCollection _Controls;
+ ///
+ /// 获取 Node 所包含的控件集合
+ ///
+ protected STNodeControlCollection Controls {
+ get { return _Controls; }
+ }
+ ///
+ /// 获取 Node 所包含的控件集合个数
+ ///
+ public int ControlsCount { get { return _Controls.Count; } }
+ ///
+ /// 获取 Node 坐标位置
+ ///
+ public Point Location { get { return new Point(this._Left, this._Top); } }
+ ///
+ /// 获取 Node 大小
+ ///
+ public Size Size { get { return new Size(this._Width, this._Height); } }
+
+ private Font _Font;
+ ///
+ /// 获取或设置 Node 字体
+ ///
+ protected Font Font {
+ get { return _Font; }
+ set {
+ if (value == _Font) return;
+ this._Font.Dispose();
+ _Font = value;
+ }
+ }
+
+ private bool _LockOption;
+ ///
+ /// 获取或设置是否锁定Option选项 锁定后不在接受连接
+ ///
+ public bool LockOption {
+ get { return _LockOption; }
+ set {
+ _LockOption = value;
+ this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight));
+ }
+ }
+
+ private bool _LockLocation;
+ ///
+ /// 获取或设置是否锁定Node位置 锁定后不可移动
+ ///
+ public bool LockLocation {
+ get { return _LockLocation; }
+ set {
+ _LockLocation = value;
+ this.Invalidate(new Rectangle(0, 0, this._Width, this._TitleHeight));
+ }
+ }
+
+ private ContextMenuStrip _ContextMenuStrip;
+ ///
+ /// 获取或设置当前Node 上下文菜单
+ ///
+ public ContextMenuStrip ContextMenuStrip {
+ get { return _ContextMenuStrip; }
+ set { _ContextMenuStrip = value; }
+ }
+
+ private object _Tag;
+ ///
+ /// 获取或设置用户自定义保存的数据
+ ///
+ public object Tag {
+ get { return _Tag; }
+ set { _Tag = value; }
+ }
+
+ private bool m_isBuildNodeSize;
+ private bool m_isBuildMarkSize;
+ private static Point m_static_pt_init = new Point(10, 10);
+
+ public STNode(/*string strTitle, int x, int y*/) {
+ //this._Title = strTitle;
+ this._Title = "Untitled";
+ this._Height = this._TitleHeight;
+ this._MarkRectangle.Height = this._Height;
+ this._Left = this._MarkRectangle.X = m_static_pt_init.X;
+ this._Top = m_static_pt_init.Y;
+ this._MarkRectangle.Y = this._Top - 30;
+ this._InputOptions = new STNodeOptionCollection(this, true);
+ this._OutputOptions = new STNodeOptionCollection(this, false);
+ this._Controls = new STNodeControlCollection(this);
+ this._BackColor = Color.FromArgb(200, 64, 64, 64);
+ this._TitleColor = Color.FromArgb(200, Color.DodgerBlue);
+ this._MarkColor = Color.FromArgb(200, Color.Brown);
+ this._Font = new Font("courier new", 8.25f);
+
+ m_sf = new StringFormat();
+ m_sf.Alignment = StringAlignment.Near;
+ m_sf.LineAlignment = StringAlignment.Center;
+ m_sf.FormatFlags = StringFormatFlags.NoWrap;
+ m_sf.SetTabStops(0, new float[] { 40 });
+ m_static_pt_init.X += 10;
+ m_static_pt_init.Y += 10;
+ this.OnCreate();
+ }
+
+ private int m_nItemHeight = 20;
+ protected StringFormat m_sf;
+ ///
+ /// 当前Node中 活动的控件
+ ///
+ protected STNodeControl m_ctrl_active;
+ ///
+ /// 当前Node中 悬停的控件
+ ///
+ protected STNodeControl m_ctrl_hover;
+
+ public void BuildSize(bool bBuildNode, bool bBuildMark, bool bRedraw) {
+ m_isBuildNodeSize = bBuildNode;
+ m_isBuildMarkSize = bBuildMark;
+ if (bRedraw) {
+ if (this._Owner != null) this._Owner.Invalidate();
+ }
+ }
+
+ internal void CheckSize(DrawingTools dt) {
+ if (m_isBuildNodeSize) {
+ Size sz = this.OnBuildNodeSize(dt);
+ this._Width = sz.Width;
+ this._Height = sz.Height;
+ m_isBuildNodeSize = false;
+ }
+ if (m_isBuildMarkSize) {
+ m_isBuildMarkSize = true;
+ if (string.IsNullOrEmpty(this._Mark)) return;
+ this._MarkRectangle = this.OnBuildMarkRectangle(dt);
+ }
+ }
+
+ internal Dictionary
OnSaveNode() {
+ Dictionary dic = new Dictionary();
+ dic.Add("Left", BitConverter.GetBytes(this._Left));
+ dic.Add("Top", BitConverter.GetBytes(this._Top));
+ dic.Add("Mark", string.IsNullOrEmpty(this._Mark) ? new byte[] { 0 } : Encoding.UTF8.GetBytes(this._Mark));
+ dic.Add("LockOption", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+ dic.Add("LockLocation", new byte[] { (byte)(this._LockLocation ? 1 : 0) });
+ this.OnSaveNode(dic);
+ return dic;
+ }
+
+ internal virtual byte[] GetSaveData() {
+ List lst = new List();
+ byte[] byData = Encoding.UTF8.GetBytes(this.GetType().GUID.ToString());
+ lst.Add((byte)byData.Length);
+ lst.AddRange(byData);
+
+ var dic = this.OnSaveNode();
+ if (dic != null) {
+ foreach (var v in dic) {
+ byData = Encoding.UTF8.GetBytes(v.Key);
+ lst.AddRange(BitConverter.GetBytes(byData.Length));
+ lst.AddRange(byData);
+ lst.AddRange(BitConverter.GetBytes(v.Value.Length));
+ lst.AddRange(v.Value);
+ }
+ }
+ return lst.ToArray();
+ }
+
+ //internal virtual byte[] GetSaveData() {
+ // List lst = new List();
+ // Type t = this.GetType();
+ // //lst.AddRange(BitConverter.GetBytes(this._Left));
+ // //lst.AddRange(BitConverter.GetBytes(this._Top));
+ // byte[] byData = Encoding.UTF8.GetBytes(t.Module.Name);
+ // lst.Add((byte)byData.Length);
+ // lst.AddRange(byData);
+ // byData = Encoding.UTF8.GetBytes(t.FullName);
+ // lst.Add((byte)byData.Length);
+ // lst.AddRange(byData);
+
+ // //if (!string.IsNullOrEmpty(this._Mark)) {
+ // // byData = Encoding.UTF8.GetBytes(this._Mark);
+ // // lst.AddRange(BitConverter.GetBytes(byData.Length));
+ // // lst.AddRange(byData);
+ // //} else lst.AddRange(new byte[] { 0, 0, 0, 0 });
+ // var dic = this.OnSaveNode();
+ // if (dic != null) {
+ // foreach (var v in dic) {
+ // byData = Encoding.UTF8.GetBytes(v.Key);
+ // lst.AddRange(BitConverter.GetBytes(byData.Length));
+ // lst.AddRange(byData);
+ // lst.AddRange(BitConverter.GetBytes(v.Value.Length));
+ // lst.AddRange(v.Value);
+ // }
+ // }
+ // return lst.ToArray();
+ //}
+
+ #region protected
+ ///
+ /// 当Node被构造时候发生
+ ///
+ protected virtual void OnCreate() { }
+ ///
+ /// 绘制整个Node
+ ///
+ /// 绘制工具
+ protected internal virtual void OnDrawNode(DrawingTools dt) {
+ dt.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
+ //Fill background
+ if (this._BackColor.A != 0) {
+ dt.SolidBrush.Color = this._BackColor;
+ dt.Graphics.FillRectangle(dt.SolidBrush, this._Left, this._Top + this._TitleHeight, this._Width, this.Height - this._TitleHeight);
+ }
+ this.OnDrawTitle(dt);
+ this.OnDrawBody(dt);
+ }
+ ///
+ /// 绘制Node标题部分
+ ///
+ /// 绘制工具
+ protected virtual void OnDrawTitle(DrawingTools dt) {
+ m_sf.Alignment = StringAlignment.Center;
+ m_sf.LineAlignment = StringAlignment.Center;
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ if (this._TitleColor.A != 0) {
+ brush.Color = this._TitleColor;
+ g.FillRectangle(brush, this.TitleRectangle);
+ }
+ if (this._LockOption) {
+ dt.Pen.Color = this.ForeColor;
+ int n = this._Top + this._TitleHeight / 2 - 5;
+ g.DrawRectangle(dt.Pen, this._Left + 3, n + 0, 6, 3);
+ g.DrawRectangle(dt.Pen, this._Left + 2, n + 3, 8, 6);
+ g.DrawLine(dt.Pen, this._Left + 6, n + 5, this._Left + 6, n + 7);
+
+ }
+ if (this._LockLocation) {
+ dt.Pen.Color = this.ForeColor;
+ brush.Color = this._ForeColor;
+ int n = this._Top + this._TitleHeight / 2 - 5;
+ g.FillRectangle(brush, this.Right - 9, n, 5, 7);
+ g.DrawLine(dt.Pen, this.Right - 10, n, this.Right - 4, n);
+ g.DrawLine(dt.Pen, this.Right - 11, n + 6, this.Right - 3, n + 6);
+ g.DrawLine(dt.Pen, this.Right - 7, n + 7, this.Right - 7, n + 9);
+ }
+ if (!string.IsNullOrEmpty(this._Title) && this._ForeColor.A != 0) {
+ brush.Color = this._ForeColor;
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
+ g.DrawString(this._Title, this._Font, brush, this.TitleRectangle, m_sf);
+ }
+ }
+ ///
+ /// 绘制Node主体部分 除去标题部分
+ ///
+ /// 绘制工具
+ protected virtual void OnDrawBody(DrawingTools dt) {
+ SolidBrush brush = dt.SolidBrush;
+ Rectangle rect = new Rectangle(this.Left + 10, this._Top + this._TitleHeight, this._Width - 20, m_nItemHeight);
+ m_sf.Alignment = StringAlignment.Near;
+ foreach (STNodeOption op in this._InputOptions) {
+ brush.Color = op.TextColor;// this._ForeColor;
+ dt.Graphics.DrawString(op.Text, this._Font, brush, rect, m_sf);
+ op.DotLeft = this.Left - 5;
+ op.DotTop = rect.Y + 5;
+ Point pt = this.OnSetOptionLocation(op);
+ op.DotLeft = pt.X;
+ op.DotTop = pt.Y;
+ this.OnDrawOptionDot(dt, op);
+ rect.Y += m_nItemHeight;
+ }
+ rect.Y = this._Top + this._TitleHeight;
+ m_sf.Alignment = StringAlignment.Far;
+ foreach (STNodeOption op in this._OutputOptions) {
+ brush.Color = op.TextColor;// this._ForeColor;
+ dt.Graphics.DrawString(op.Text, this._Font, brush, rect, m_sf);
+ op.DotLeft = this.Left + this.Width - 5;
+ op.DotTop = rect.Y + 5;
+ Point pt = this.OnSetOptionLocation(op);
+ op.DotLeft = pt.X;
+ op.DotTop = pt.Y;
+ this.OnDrawOptionDot(dt, op);
+ rect.Y += m_nItemHeight;
+ }
+ if (this._Controls.Count != 0) { //绘制子控件
+ //将坐标原点与节点对齐
+ dt.Graphics.TranslateTransform(this._Left, this._Top + this._TitleHeight);
+ Point pt = Point.Empty; //当前需要偏移的量
+ Point pt_last = Point.Empty; //最后一个控件相对于节点的坐标
+ foreach (STNodeControl v in this._Controls) {
+ pt.X = v.Left - pt.X;
+ pt.Y = v.Top - pt.Y;
+ pt_last = v.Location;
+ dt.Graphics.TranslateTransform(pt.X, pt.Y); //将原点坐标移动至控件位置
+ v.OnPaint(dt);
+ }
+ //dt.Graphics.TranslateTransform(-pt_last.X, -pt_last.Y); 还原坐标
+ dt.Graphics.TranslateTransform(-this._Left - pt_last.X, -this._Top - this._TitleHeight - pt_last.Y);
+ }
+ }
+ ///
+ /// 绘制标记信息
+ ///
+ /// 绘制工具
+ protected internal virtual void OnDrawMark(DrawingTools dt) {
+ if (string.IsNullOrEmpty(this._Mark)) return;
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ m_sf.LineAlignment = StringAlignment.Center;
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
+ brush.Color = this._MarkColor;
+ g.FillRectangle(brush, this._MarkRectangle); //填充背景色
+
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //确定文本绘制所需大小
+ var sz = g.MeasureString(this.Mark, this.Font, this._MarkRectangle.Width);
+ brush.Color = this._ForeColor;
+ if (sz.Height > m_nItemHeight || sz.Width > this._MarkRectangle.Width) { //如果超过绘图区 则绘制部分
+ Rectangle rect = new Rectangle(this._MarkRectangle.Left + 2, this._MarkRectangle.Top + 2, this._MarkRectangle.Width - 20, 16);
+ m_sf.Alignment = StringAlignment.Near;
+ g.DrawString(this._MarkLines[0], this._Font, brush, rect, m_sf);
+ m_sf.Alignment = StringAlignment.Far;
+ rect.Width = this._MarkRectangle.Width - 5;
+ g.DrawString("+", this._Font, brush, rect, m_sf); // + 表示超过绘图区
+ } else {
+ m_sf.Alignment = StringAlignment.Near;
+ g.DrawString(this._MarkLines[0].Trim(), this._Font, brush, this._MarkRectangle, m_sf);
+ }
+ }
+ ///
+ /// 绘制选项连线的点
+ ///
+ /// 绘制工具
+ /// 指定的选项
+ protected virtual void OnDrawOptionDot(DrawingTools dt, STNodeOption op) {
+ Graphics g = dt.Graphics;
+ Pen pen = dt.Pen;
+ SolidBrush brush = dt.SolidBrush;
+ var t = typeof(object);
+ if (op.DotColor != Color.Transparent) //设置颜色
+ brush.Color = op.DotColor;
+ else {
+ if (op.DataType == t)
+ pen.Color = this.Owner.UnknownTypeColor;
+ else
+ brush.Color = this.Owner.TypeColor.ContainsKey(op.DataType) ? this.Owner.TypeColor[op.DataType] : this.Owner.UnknownTypeColor;
+ }
+ if (op.IsSingle) { //单连接 圆形
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
+ if (op.DataType == t) { //未知类型绘制 否则填充
+ g.DrawEllipse(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1);
+ } else
+ g.FillEllipse(brush, op.DotRectangle);
+ } else { //多连接 矩形
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
+ if (op.DataType == t) {
+ g.DrawRectangle(pen, op.DotRectangle.X, op.DotRectangle.Y, op.DotRectangle.Width - 1, op.DotRectangle.Height - 1);
+ } else
+ g.FillRectangle(brush, op.DotRectangle);
+ }
+ }
+ ///
+ /// 当计算Option位置时候发生
+ ///
+ /// 需要计算的Option
+ /// 新的位置
+ protected virtual Point OnSetOptionLocation(STNodeOption op) {
+ return new Point(op.DotLeft, op.DotTop);
+ }
+ ///
+ /// 计算当前Node所需要的矩形区域
+ /// 若需要自己重绘Node 则应当重写此函数 以确定绘图区域大小
+ /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
+ /// 但是并不会被STNodeEditor所接受 并触发对应事件
+ ///
+ protected virtual Size OnBuildNodeSize(DrawingTools dt) {
+ int nInputHeight = 0, nOutputHeight = 0;
+ foreach (STNodeOption op in this._InputOptions) nInputHeight += m_nItemHeight;
+ foreach (STNodeOption op in this._OutputOptions) nOutputHeight += m_nItemHeight;
+ int nHeight = this._TitleHeight + (nInputHeight > nOutputHeight ? nInputHeight : nOutputHeight);
+
+ SizeF szf_input = SizeF.Empty, szf_output = SizeF.Empty;
+ foreach (STNodeOption v in this._InputOptions) {
+ if (string.IsNullOrEmpty(v.Text)) continue;
+ SizeF szf = dt.Graphics.MeasureString(v.Text, this._Font);
+ if (szf.Width > szf_input.Width) szf_input = szf;
+ }
+ foreach (STNodeOption v in this._OutputOptions) {
+ if (string.IsNullOrEmpty(v.Text)) continue;
+ SizeF szf = dt.Graphics.MeasureString(v.Text, this._Font);
+ if (szf.Width > szf_output.Width) szf_output = szf;
+ }
+ int nWidth = (int)(szf_input.Width + szf_output.Width + 25);
+ if (!string.IsNullOrEmpty(this.Title)) szf_input = dt.Graphics.MeasureString(this.Title, this.Font);
+ if (szf_input.Width + 30 > nWidth) nWidth = (int)szf_input.Width + 30;
+ return new Size(nWidth, nHeight);
+ }
+ ///
+ /// 计算当前Mark所需要的矩形区域
+ /// 若需要自己重绘Mark 则应当重写此函数 以确定绘图区域大小
+ /// 返回的大小并不会限制绘制区域 任然可以在此区域之外绘制
+ /// 但是并不会被STNodeEditor所接受 并触发对应事件
+ ///
+ protected virtual Rectangle OnBuildMarkRectangle(DrawingTools dt) {
+ //if (string.IsNullOrEmpty(this._Mark)) return Rectangle.Empty;
+ return new Rectangle(this._Left, this._Top - 30, this._Width, 20);
+ }
+ ///
+ /// 当需要保存时候 此Node有哪些需要额外保存的数据
+ /// 注意: 保存时并不会进行序列化 还原时候仅重新通过空参数构造器创建此Node
+ /// 然后调用 OnLoadNode() 将保存的数据进行还原
+ ///
+ /// 需要保存的数据
+ protected virtual void OnSaveNode(Dictionary dic) { }
+ ///
+ /// 当还原该节点时候会将 OnSaveNode() 所返回的数据重新传入此函数
+ ///
+ /// 保存时候的数据
+ protected internal virtual void OnLoadNode(Dictionary dic) {
+ if (dic.ContainsKey("Left")) this._Left = BitConverter.ToInt32(dic["Left"], 0);
+ if (dic.ContainsKey("Top")) this._Top = BitConverter.ToInt32(dic["Top"], 0);
+ if (dic.ContainsKey("Mark")) {
+ string strText = Encoding.UTF8.GetString(dic["Mark"]);
+ if (strText != "\0") this.Mark = strText;
+ }
+ if (dic.ContainsKey("LockOption")) this._LockOption = dic["LockOption"][0] == 1;
+ if (dic.ContainsKey("LockLocation")) this._LockLocation = dic["LockLocation"][0] == 1;
+ }
+
+ //[event]===========================[event]==============================[event]============================[event]
+
+ protected internal virtual void OnGotFocus(EventArgs e) { }
+
+ protected internal virtual void OnLostFocus(EventArgs e) { }
+
+ protected internal virtual void OnMouseEnter(EventArgs e) { }
+
+ protected internal virtual void OnMouseDown(MouseEventArgs e) {
+ Point pt = e.Location;
+ pt.Y -= this._TitleHeight;
+ for (int i = this._Controls.Count - 1; i >= 0; i--) {
+ var c = this._Controls[i];
+ if (c.DisplayRectangle.Contains(pt)) {
+ c.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta));
+ if (m_ctrl_active != c) {
+ c.OnGotFocus(new EventArgs());
+ if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(new EventArgs());
+ m_ctrl_active = c;
+ }
+ return;
+ }
+ }
+ if (m_ctrl_active != null) m_ctrl_active.OnLostFocus(new EventArgs());
+ m_ctrl_active = null;
+ }
+
+ protected internal virtual void OnMouseMove(MouseEventArgs e) {
+ Point pt = e.Location;
+ pt.Y -= this._TitleHeight;
+ for (int i = this._Controls.Count - 1; i >= 0; i--) {
+ var c = this._Controls[i];
+ if (c.DisplayRectangle.Contains(pt)) {
+ if (m_ctrl_hover != this._Controls[i]) {
+ c.OnMouseEnter(new EventArgs());
+ if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(new EventArgs());
+ m_ctrl_hover = c;
+ }
+ m_ctrl_hover.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta));
+ return;
+ }
+ }
+ if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(new EventArgs());
+ m_ctrl_hover = null;
+ }
+
+ protected internal virtual void OnMouseUp(MouseEventArgs e) {
+ Point pt = e.Location;
+ pt.Y -= this._TitleHeight;
+ if (m_ctrl_active != null) {
+ m_ctrl_active.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks,
+ e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta));
+ }
+ //for (int i = this._Controls.Count - 1; i >= 0; i--) {
+ // var c = this._Controls[i];
+ // if (c.DisplayRectangle.Contains(pt)) {
+ // c.OnMouseUp(new MouseEventArgs(e.Button, e.Clicks, e.X - c.Left, pt.Y - c.Top, e.Delta));
+ // return;
+ // }
+ //}
+ }
+
+ protected internal virtual void OnMouseLeave(EventArgs e) {
+ if (m_ctrl_hover != null) m_ctrl_hover.OnMouseLeave(e);
+ m_ctrl_hover = null;
+ }
+
+ protected internal virtual void OnMouseClick(MouseEventArgs e) {
+ Point pt = e.Location;
+ pt.Y -= this._TitleHeight;
+ if (m_ctrl_active != null)
+ m_ctrl_active.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_active.Left, pt.Y - m_ctrl_active.Top, e.Delta));
+ }
+
+ protected internal virtual void OnMouseWheel(MouseEventArgs e) {
+ Point pt = e.Location;
+ pt.Y -= this._TitleHeight;
+ if (m_ctrl_hover != null) {
+ m_ctrl_hover.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks, e.X - m_ctrl_hover.Left, pt.Y - m_ctrl_hover.Top, e.Delta));
+ return;
+ }
+ }
+ protected internal virtual void OnMouseHWheel(MouseEventArgs e) {
+ if (m_ctrl_hover != null) {
+ m_ctrl_hover.OnMouseHWheel(e);
+ return;
+ }
+ }
+
+ protected internal virtual void OnKeyDown(KeyEventArgs e) {
+ if (m_ctrl_active != null) m_ctrl_active.OnKeyDown(e);
+ }
+ protected internal virtual void OnKeyUp(KeyEventArgs e) {
+ if (m_ctrl_active != null) m_ctrl_active.OnKeyUp(e);
+ }
+ protected internal virtual void OnKeyPress(KeyPressEventArgs e) {
+ if (m_ctrl_active != null) m_ctrl_active.OnKeyPress(e);
+ }
+
+ protected internal virtual void OnMove(EventArgs e) { }
+ protected internal virtual void OnResize(EventArgs e) { }
+
+
+ ///
+ /// 当所有者发生改变时候发生
+ ///
+ protected virtual void OnOwnerChanged() { }
+ ///
+ /// 当选中状态改变时候发生
+ ///
+ protected virtual void OnSelectedChanged() { }
+ ///
+ /// 当活动状态改变时候发生
+ ///
+ protected virtual void OnActiveChanged() { }
+
+ #endregion protected
+
+ ///
+ /// 重绘Node
+ ///
+ public void Invalidate() {
+ if (this._Owner != null) {
+ this._Owner.Invalidate(this._Owner.CanvasToControl(new Rectangle(this._Left - 5, this._Top - 5, this._Width + 10, this._Height + 10)));
+ }
+ }
+ ///
+ /// 重绘 Node 指定区域
+ ///
+ /// Node 指定区域
+ public void Invalidate(Rectangle rect) {
+ rect.X += this._Left;
+ rect.Y += this._Top;
+ if (this._Owner != null) {
+ this._Owner.Invalidate(this._Owner.CanvasToControl(rect));
+ }
+ }
+ ///
+ /// 获取此Node所包含的输入Option集合
+ ///
+ /// Option集合
+ public STNodeOption[] GetInputOptions() {
+ STNodeOption[] ops = new STNodeOption[this._InputOptions.Count];
+ for (int i = 0; i < this._InputOptions.Count; i++) ops[i] = this._InputOptions[i];
+ return ops;
+ }
+ ///
+ /// 获取此Node所包含的输出Option集合
+ ///
+ /// Option集合
+ public STNodeOption[] GetOutputOptions() {
+ STNodeOption[] ops = new STNodeOption[this._OutputOptions.Count];
+ for (int i = 0; i < this._OutputOptions.Count; i++) ops[i] = this._OutputOptions[i];
+ return ops;
+ }
+ ///
+ /// 设置Node的选中状态
+ ///
+ /// 是否选中
+ /// 是否重绘
+ public void SetSelected(bool bSelected, bool bRedraw) {
+ if (this._IsSelected == bSelected) return;
+ this._IsSelected = bSelected;
+ if (bRedraw) this.Invalidate();
+ this.OnSelectedChanged();
+ }
+ public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); }
+ public IAsyncResult BeginInvoke(Delegate method, params object[] args) {
+ if (this._Owner == null) return null;
+ return this._Owner.BeginInvoke(method, args);
+ }
+ public object Invoke(Delegate method) { return this.Invoke(method, null); }
+ public object Invoke(Delegate method, params object[] args) {
+ if (this._Owner == null) return null;
+ return this._Owner.Invoke(method, args);
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeCollection.cs b/ST.Library.UI/STNodeEditor/STNodeCollection.cs
new file mode 100755
index 0000000..18eac67
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeCollection.cs
@@ -0,0 +1,229 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Collections;
+using System.Drawing;
+
+namespace ST.Library.UI
+{
+ public class STNodeCollection : IList, ICollection, IEnumerable
+ {
+ private int _Count;
+ public int Count { get { return _Count; } }
+ private STNode[] m_nodes;
+ private STNodeEditor m_owner;
+
+ internal STNodeCollection(STNodeEditor owner) {
+ if (owner == null) throw new ArgumentNullException("所有者不能为空");
+ m_owner = owner;
+ m_nodes = new STNode[4];
+ }
+
+ public int Add(STNode node) {
+ if (node == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(1);
+ int nIndex = this.IndexOf(node);
+ if (-1 == nIndex) {
+ nIndex = this._Count;
+ node.Owner = m_owner;
+ node.BuildSize(true, true, false);
+ m_nodes[this._Count++] = node;
+ m_owner.BuildBounds();
+ m_owner.OnNodeAdded(new STNodeEditorEventArgs(node));
+ m_owner.Invalidate();
+ //m_owner.Invalidate(m_owner.CanvasToControl(new Rectangle(node.Left - 5, node.Top - 5, node.Width + 10, node.Height + 10)));
+ //Console.WriteLine(node.Rectangle);
+ }
+ return nIndex;
+ }
+
+ public void AddRange(STNode[] nodes) {
+ if (nodes == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(nodes.Length);
+ foreach (var n in nodes) {
+ if (n == null) throw new ArgumentNullException("添加对象不能为空");
+ if (-1 == this.IndexOf(n)) {
+ n.Owner = m_owner;
+ m_nodes[this._Count++] = n;
+ }
+ m_owner.OnNodeAdded(new STNodeEditorEventArgs(n));
+ }
+ m_owner.Invalidate();
+ m_owner.BuildBounds();
+ }
+
+ public void Clear() {
+ for (int i = 0; i < this._Count; i++) {
+ m_nodes[i].Owner = null;
+ foreach (STNodeOption op in m_nodes[i].InputOptions) op.DisConnectionAll();
+ foreach (STNodeOption op in m_nodes[i].OutputOptions) op.DisConnectionAll();
+ m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[i]));
+ }
+ this._Count = 0;
+ m_nodes = new STNode[4];
+ m_owner.BuildBounds();
+ m_owner.ScaleCanvas(1, 0, 0); //当不存在节点时候 坐标系回归
+ m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
+ m_owner.Invalidate(); //如果画布位置和缩放处于初始状态 上面两行代码并不会造成控件重绘
+ }
+
+ public bool Contains(STNode node) {
+ return this.IndexOf(node) != -1;
+ }
+
+ public int IndexOf(STNode node) {
+ return Array.IndexOf(m_nodes, node);
+ }
+
+ public void Insert(int nIndex, STNode node) {
+ if (nIndex < 0 || nIndex >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ if (node == null)
+ throw new ArgumentNullException("插入对象不能为空");
+ this.EnsureSpace(1);
+ for (int i = this._Count; i > nIndex; i--)
+ m_nodes[i] = m_nodes[i - 1];
+ node.Owner = m_owner;
+ m_nodes[nIndex] = node;
+ this._Count++;
+ node.BuildSize(true, true,false);
+ m_owner.Invalidate();
+ m_owner.BuildBounds();
+ }
+
+ public bool IsFixedSize {
+ get { return false; }
+ }
+
+ public bool IsReadOnly {
+ get { return false; }
+ }
+
+ public void Remove(STNode node) {
+ int nIndex = this.IndexOf(node);
+ if (nIndex != -1) this.RemoveAt(nIndex);
+ }
+
+ public void RemoveAt(int nIndex) {
+ if (nIndex < 0 || nIndex >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ m_nodes[nIndex].Owner = null;
+ m_owner.OnNodeRemoved(new STNodeEditorEventArgs(m_nodes[nIndex]));
+ this._Count--;
+ for (int i = nIndex, Len = this._Count; i < Len; i++)
+ m_nodes[i] = m_nodes[i + 1];
+ if (this._Count == 0) { //当不存在节点时候 坐标系回归
+ m_owner.ScaleCanvas(1, 0, 0);
+ m_owner.MoveCanvas(10, 10, true, CanvasMoveArgs.All);
+ } else {
+ m_owner.Invalidate();
+ m_owner.BuildBounds();
+ }
+ }
+
+ public STNode this[int nIndex] {
+ get {
+ if (nIndex < 0 || nIndex >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ return m_nodes[nIndex];
+ }
+ set { throw new InvalidOperationException("禁止重新赋值元素"); }
+ }
+
+ public void CopyTo(Array array, int index) {
+ if (array == null)
+ throw new ArgumentNullException("数组不能为空");
+ m_nodes.CopyTo(array, index);
+ }
+
+ public bool IsSynchronized {
+ get { return true; }
+ }
+
+ public object SyncRoot {
+ get { return this; }
+ }
+
+ public IEnumerator GetEnumerator() {
+ for (int i = 0, Len = this._Count; i < Len; i++)
+ yield return m_nodes[i];
+ }
+ ///
+ /// 确认空间是否足够 空间不足扩大容量
+ ///
+ /// 需要增加的个数
+ private void EnsureSpace(int elements) {
+ if (elements + this._Count > m_nodes.Length) {
+ STNode[] arrTemp = new STNode[Math.Max(m_nodes.Length * 2, elements + this._Count)];
+ m_nodes.CopyTo(arrTemp, 0);
+ m_nodes = arrTemp;
+ }
+ }
+ //============================================================================
+ int IList.Add(object value) {
+ return this.Add((STNode)value);
+ }
+
+ void IList.Clear() {
+ this.Clear();
+ }
+
+ bool IList.Contains(object value) {
+ return this.Contains((STNode)value);
+ }
+
+ int IList.IndexOf(object value) {
+ return this.IndexOf((STNode)value);
+ }
+
+ void IList.Insert(int index, object value) {
+ this.Insert(index, (STNode)value);
+ }
+
+ bool IList.IsFixedSize {
+ get { return this.IsFixedSize; }
+ }
+
+ bool IList.IsReadOnly {
+ get { return this.IsReadOnly; }
+ }
+
+ void IList.Remove(object value) {
+ this.Remove((STNode)value);
+ }
+
+ void IList.RemoveAt(int index) {
+ this.RemoveAt(index);
+ }
+
+ object IList.this[int index] {
+ get {
+ return this[index];
+ }
+ set {
+ this[index] = (STNode)value;
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index) {
+ this.CopyTo(array, index);
+ }
+
+ int ICollection.Count {
+ get { return this._Count; }
+ }
+
+ bool ICollection.IsSynchronized {
+ get { return this.IsSynchronized; }
+ }
+
+ object ICollection.SyncRoot {
+ get { return this.SyncRoot; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ return this.GetEnumerator();
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeControl.cs b/ST.Library.UI/STNodeEditor/STNodeControl.cs
new file mode 100755
index 0000000..ba78121
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeControl.cs
@@ -0,0 +1,235 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.Drawing;
+
+namespace ST.Library.UI
+{
+ public class STNodeControl
+ {
+ private STNode _Owner;
+
+ public STNode Owner {
+ get { return _Owner; }
+ internal set { _Owner = value; }
+ }
+
+ private int _Left;
+
+ public int Left {
+ get { return _Left; }
+ set {
+ _Left = value;
+ this.OnMove(new EventArgs());
+ this.Invalidate();
+ }
+ }
+
+ private int _Top;
+
+ public int Top {
+ get { return _Top; }
+ set {
+ _Top = value;
+ this.OnMove(new EventArgs());
+ this.Invalidate();
+ }
+ }
+
+ private int _Width;
+
+ public int Width {
+ get { return _Width; }
+ set {
+ _Width = value;
+ this.OnResize(new EventArgs());
+ this.Invalidate();
+ }
+ }
+
+ private int _Height;
+
+ public int Height {
+ get { return _Height; }
+ set {
+ _Height = value;
+ this.OnResize(new EventArgs());
+ this.Invalidate();
+ }
+ }
+
+ public Point Location {
+ get { return new Point(this._Left, this._Top); }
+ }
+ public Size Size {
+ get { return new Size(this._Width, this._Height); }
+ }
+ public Rectangle DisplayRectangle {
+ get { return new Rectangle(this._Left, this._Top, this._Width, this._Height); }
+ }
+ public Rectangle ClientRectangle {
+ get { return new Rectangle(0, 0, this._Width, this._Height); }
+ }
+
+ private Color _BackColor = Color.FromArgb(127, 0, 0, 0);
+
+ public Color BackColor {
+ get { return _BackColor; }
+ set {
+ _BackColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _ForeColor = Color.White;
+
+ public Color ForeColor {
+ get { return _ForeColor; }
+ set {
+ _ForeColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private string _Text = "STNCTRL";
+
+ public string Text {
+ get { return _Text; }
+ set {
+ _Text = value;
+ this.Invalidate();
+ }
+ }
+
+ private Font _Font;
+
+ public Font Font {
+ get { return _Font; }
+ set {
+ if (value == _Font) return;
+ if (value == null) throw new ArgumentNullException("值不能为空");
+ _Font = value;
+ this.Invalidate();
+ }
+ }
+
+ protected StringFormat m_sf;
+
+ public STNodeControl() {
+ m_sf = new StringFormat();
+ m_sf.Alignment = StringAlignment.Center;
+ m_sf.LineAlignment = StringAlignment.Center;
+ this._Font = new Font("courier new", 8.25f);
+ this.Width = 75;
+ this.Height = 23;
+ }
+
+ protected internal virtual void OnPaint(DrawingTools dt) {
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
+ g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+ brush.Color = this._BackColor;
+ g.FillRectangle(brush, 0, 0, this.Width, this.Height);
+ if (!string.IsNullOrEmpty(this._Text)) {
+ brush.Color = this._ForeColor;
+ g.DrawString(this._Text, this._Font, brush, this.ClientRectangle, m_sf);
+ }
+ }
+
+ public void Invalidate() {
+ if (this._Owner == null) return;
+ this._Owner.Invalidate(new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height));
+ }
+
+ public void Invalidate(Rectangle rect) {
+ if (this._Owner == null) return;
+ this._Owner.Invalidate(this.RectangleToParent(rect));
+ }
+
+ public Rectangle RectangleToParent(Rectangle rect) {
+ return new Rectangle(this._Left, this._Top + this._Owner.TitleHeight, this.Width, this.Height);
+ }
+
+ public event EventHandler GotFocus;
+ public event EventHandler LostFocus;
+ public event EventHandler MouseEnter;
+ public event EventHandler MouseLeave;
+ public event MouseEventHandler MouseDown;
+ public event MouseEventHandler MouseMove;
+ public event MouseEventHandler MouseUp;
+ public event MouseEventHandler MouseClick;
+ public event MouseEventHandler MouseWheel;
+ public event EventHandler MouseHWheel;
+
+ public event KeyEventHandler KeyDown;
+ public event KeyEventHandler KeyUp;
+ public event KeyPressEventHandler KeyPress;
+
+ public event EventHandler Move;
+ public event EventHandler Resize;
+
+
+ protected internal virtual void OnGotFocus(EventArgs e) {
+ if (this.GotFocus != null) this.GotFocus(this, e);
+ }
+ protected internal virtual void OnLostFocus(EventArgs e) {
+ if (this.LostFocus != null) this.LostFocus(this, e);
+ }
+ protected internal virtual void OnMouseEnter(EventArgs e) {
+ if (this.MouseEnter != null) this.MouseEnter(this, e);
+ }
+ protected internal virtual void OnMouseLeave(EventArgs e) {
+ if (this.MouseLeave != null) this.MouseLeave(this, e);
+ }
+ protected internal virtual void OnMouseDown(MouseEventArgs e) {
+ if (this.MouseDown != null) this.MouseDown(this, e);
+ }
+ protected internal virtual void OnMouseMove(MouseEventArgs e) {
+ if (this.MouseMove != null) this.MouseMove(this, e);
+ }
+ protected internal virtual void OnMouseUp(MouseEventArgs e) {
+ if (this.MouseUp != null) this.MouseUp(this, e);
+ }
+ protected internal virtual void OnMouseClick(MouseEventArgs e) {
+ if (this.MouseClick != null) this.MouseClick(this, e);
+ }
+ protected internal virtual void OnMouseWheel(MouseEventArgs e) {
+ if (this.MouseWheel != null) this.MouseWheel(this, e);
+ }
+ protected internal virtual void OnMouseHWheel(MouseEventArgs e) {
+ if (this.MouseHWheel != null) this.MouseHWheel(this, e);
+ }
+
+ protected internal virtual void OnKeyDown(KeyEventArgs e) {
+ if (this.KeyDown != null) this.KeyDown(this, e);
+ }
+ protected internal virtual void OnKeyUp(KeyEventArgs e) {
+ if (this.KeyUp != null) this.KeyUp(this, e);
+ }
+ protected internal virtual void OnKeyPress(KeyPressEventArgs e) {
+ if (this.KeyPress != null) this.KeyPress(this, e);
+ }
+
+ protected internal virtual void OnMove(EventArgs e) {
+ if (this.Move != null) this.Move(this, e);
+ }
+
+ protected internal virtual void OnResize(EventArgs e) {
+ if (this.Resize != null) this.Resize(this, e);
+ }
+
+ public IAsyncResult BeginInvoke(Delegate method) { return this.BeginInvoke(method, null); }
+ public IAsyncResult BeginInvoke(Delegate method, params object[] args) {
+ if (this._Owner == null) return null;
+ return this._Owner.BeginInvoke(method, args);
+ }
+ public object Invoke(Delegate method) { return this.Invoke(method, null); }
+ public object Invoke(Delegate method, params object[] args) {
+ if (this._Owner == null) return null;
+ return this._Owner.Invoke(method, args);
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeControlCollection.cs b/ST.Library.UI/STNodeEditor/STNodeControlCollection.cs
new file mode 100755
index 0000000..b0a9ad1
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeControlCollection.cs
@@ -0,0 +1,214 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Collections;
+
+namespace ST.Library.UI
+{
+ public class STNodeControlCollection: IList, ICollection, IEnumerable
+ {
+ /*
+ * 为了确保安全在STNode中 仅继承者才能够访问集合
+ */
+ private int _Count;
+ public int Count { get { return _Count; } }
+ private STNodeControl[] m_controls;
+ private STNode m_owner;
+
+ internal STNodeControlCollection(STNode owner) {
+ if (owner == null) throw new ArgumentNullException("所有者不能为空");
+ m_owner = owner;
+ m_controls = new STNodeControl[4];
+ }
+
+ public int Add(STNodeControl control) {
+ if (control == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(1);
+ int nIndex = this.IndexOf(control);
+ if (-1 == nIndex) {
+ nIndex = this._Count;
+ control.Owner = m_owner;
+ m_controls[this._Count++] = control;
+ this.Redraw();
+ }
+ return nIndex;
+ }
+
+ public void AddRange(STNodeControl[] controls) {
+ if (controls == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(controls.Length);
+ foreach (var op in controls) {
+ if (op == null) throw new ArgumentNullException("添加对象不能为空");
+ if (-1 == this.IndexOf(op)) {
+ op.Owner = m_owner;
+ m_controls[this._Count++] = op;
+ }
+ }
+ this.Redraw();
+ }
+
+ public void Clear() {
+ for (int i = 0; i < this._Count; i++) m_controls[i].Owner = null;
+ this._Count = 0;
+ m_controls = new STNodeControl[4];
+ this.Redraw();
+ }
+
+ public bool Contains(STNodeControl option) {
+ return this.IndexOf(option) != -1;
+ }
+
+ public int IndexOf(STNodeControl option) {
+ return Array.IndexOf(m_controls, option);
+ }
+
+ public void Insert(int index, STNodeControl control) {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ if (control == null)
+ throw new ArgumentNullException("插入对象不能为空");
+ this.EnsureSpace(1);
+ for (int i = this._Count; i > index; i--)
+ m_controls[i] = m_controls[i - 1];
+ control.Owner = m_owner;
+ m_controls[index] = control;
+ this._Count++;
+ this.Redraw();
+ }
+
+ public bool IsFixedSize {
+ get { return false; }
+ }
+
+ public bool IsReadOnly {
+ get { return false; }
+ }
+
+ public void Remove(STNodeControl control) {
+ int nIndex = this.IndexOf(control);
+ if (nIndex != -1) this.RemoveAt(nIndex);
+ }
+
+ public void RemoveAt(int index) {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ this._Count--;
+ m_controls[index].Owner = null;
+ for (int i = index, Len = this._Count; i < Len; i++)
+ m_controls[i] = m_controls[i + 1];
+ this.Redraw();
+ }
+
+ public STNodeControl this[int index] {
+ get {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ return m_controls[index];
+ }
+ set { throw new InvalidOperationException("禁止重新赋值元素"); }
+ }
+
+ public void CopyTo(Array array, int index) {
+ if (array == null)
+ throw new ArgumentNullException("数组不能为空");
+ m_controls.CopyTo(array, index);
+ }
+
+ public bool IsSynchronized {
+ get { return true; }
+ }
+
+ public object SyncRoot {
+ get { return this; }
+ }
+
+ public IEnumerator GetEnumerator() {
+ for (int i = 0, Len = this._Count; i < Len; i++)
+ yield return m_controls[i];
+ }
+ ///
+ /// 确认空间是否足够 空间不足扩大容量
+ ///
+ /// 需要增加的个数
+ private void EnsureSpace(int elements) {
+ if (elements + this._Count > m_controls.Length) {
+ STNodeControl[] arrTemp = new STNodeControl[Math.Max(m_controls.Length * 2, elements + this._Count)];
+ m_controls.CopyTo(arrTemp, 0);
+ m_controls = arrTemp;
+ }
+ }
+
+ protected void Redraw() {
+ if (m_owner != null && m_owner.Owner != null) {
+ //m_owner.BuildSize();
+ m_owner.Owner.Invalidate(m_owner.Owner.CanvasToControl(m_owner.Rectangle));
+ }
+ }
+ //===================================================================================
+ int IList.Add(object value) {
+ return this.Add((STNodeControl)value);
+ }
+
+ void IList.Clear() {
+ this.Clear();
+ }
+
+ bool IList.Contains(object value) {
+ return this.Contains((STNodeControl)value);
+ }
+
+ int IList.IndexOf(object value) {
+ return this.IndexOf((STNodeControl)value);
+ }
+
+ void IList.Insert(int index, object value) {
+ this.Insert(index, (STNodeControl)value);
+ }
+
+ bool IList.IsFixedSize {
+ get { return this.IsFixedSize; }
+ }
+
+ bool IList.IsReadOnly {
+ get { return this.IsReadOnly; }
+ }
+
+ void IList.Remove(object value) {
+ this.Remove((STNodeControl)value);
+ }
+
+ void IList.RemoveAt(int index) {
+ this.RemoveAt(index);
+ }
+
+ object IList.this[int index] {
+ get {
+ return this[index];
+ }
+ set {
+ this[index] = (STNodeControl)value;
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index) {
+ this.CopyTo(array, index);
+ }
+
+ int ICollection.Count {
+ get { return this._Count; }
+ }
+
+ bool ICollection.IsSynchronized {
+ get { return this.IsSynchronized; }
+ }
+
+ object ICollection.SyncRoot {
+ get { return this.SyncRoot; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ return this.GetEnumerator();
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeEditor.cs b/ST.Library.UI/STNodeEditor/STNodeEditor.cs
new file mode 100755
index 0000000..7b9de23
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeEditor.cs
@@ -0,0 +1,1909 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using System.IO;
+using System.Windows.Forms;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Threading;
+using System.ComponentModel;
+using System.Reflection;
+
+namespace ST.Library.UI
+{
+ public class STNodeEditor : Control
+ {
+ private const UInt32 WM_MOUSEHWHEEL = 0x020E;
+ protected static readonly Type m_type_node = typeof(STNode);
+
+ #region protected enum,struct --------------------------------------------------------------------------------------
+
+ protected enum CanvasAction //当前鼠标移动操作表示进行下列哪一个行为
+ {
+ None, //无
+ MoveNode, //正在移动 Node
+ ConnectOption, //正在连接 Option
+ SelectRectangle, //正在选择矩形区域
+ DrawMarkDetails //正在绘制标记信息详情
+ }
+
+ protected struct MagnetInfo
+ {
+ public bool XMatched; //X轴是否有磁铁匹配上
+ public bool YMatched;
+ public int X; //与X轴那个数字匹配上
+ public int Y;
+ public int OffsetX; //当前节点X位置与匹配上的X的相对偏移
+ public int OffsetY;
+ }
+
+ #endregion
+
+ #region Properties ------------------------------------------------------------------------------------------------------
+
+ private float _CanvasOffsetX;
+ ///
+ /// 获取画布原点相对于控件 X 方向上的偏移位置
+ ///
+ [Browsable(false)]
+ public float CanvasOffsetX {
+ get { return _CanvasOffsetX; }
+ }
+
+ private float _CanvasOffsetY;
+ ///
+ /// 获取画布原点相对于控件 Y 方向上的偏移位置
+ ///
+ [Browsable(false)]
+ public float CanvasOffsetY {
+ get { return _CanvasOffsetY; }
+ }
+
+ private PointF _CanvasOffset;
+ ///
+ /// 获取画布原点相对于控件偏移位置
+ ///
+ [Browsable(false)]
+ public PointF CanvasOffset {
+ get {
+ _CanvasOffset.X = _CanvasOffsetX;
+ _CanvasOffset.Y = _CanvasOffsetY;
+ return _CanvasOffset;
+ }
+ }
+
+ private Rectangle _CanvasValidBounds;
+ ///
+ /// 获取画布中的有被用到的有效区域
+ ///
+ [Browsable(false)]
+ public Rectangle CanvasValidBounds {
+ get { return _CanvasValidBounds; }
+ }
+
+ private float _CanvasScale = 1;
+ ///
+ /// 获取画布的缩放比例
+ ///
+ [Browsable(false)]
+ public float CanvasScale {
+ get { return _CanvasScale; }
+ }
+
+ private float _Curvature = 0.3F;
+ ///
+ /// 获取或设置 Option 之间连线的曲度
+ ///
+ [Browsable(false)]
+ public float Curvature {
+ get { return _Curvature; }
+ set {
+ if (value < 0) value = 0;
+ if (value > 1) value = 1;
+ _Curvature = value;
+ if (m_dic_gp_info.Count != 0) this.BuildLinePath();
+ }
+ }
+
+ private bool _Magnet = true;
+ ///
+ /// 获取或设置移动画布中 Node 时候 是否启用磁铁效果
+ ///
+ [Description("获取或设置移动画布中 Node 时候 是否启用磁铁效果"), DefaultValue(true)]
+ public bool Magnet {
+ get { return _Magnet; }
+ set { _Magnet = value; }
+ }
+
+ private bool _ShowBorder = true;
+ ///
+ /// 获取或设置 移动画布中是否显示 Node 边框
+ ///
+ [Description("获取或设置 移动画布中是否显示 Node 边框"), DefaultValue(true)]
+ public bool ShowBorder {
+ get { return _ShowBorder; }
+ set {
+ _ShowBorder = value;
+ this.Invalidate();
+ }
+ }
+
+ private bool _ShowGrid = true;
+ ///
+ /// 获取或设置画布中是否绘制背景网格线条
+ ///
+ [Description("获取或设置画布中是否绘制背景网格线条"), DefaultValue(true)]
+ public bool ShowGrid {
+ get { return _ShowGrid; }
+ set {
+ _ShowGrid = value;
+ this.Invalidate();
+ }
+ }
+
+ private bool _ShowLocation = true;
+ ///
+ /// 获取或设置是否在画布边缘显示超出视角的 Node 位置信息
+ ///
+ [Description("获取或设置是否在画布边缘显示超出视角的 Node 位置信息"), DefaultValue(true)]
+ public bool ShowLocation {
+ get { return _ShowLocation; }
+ set {
+ _ShowLocation = value;
+ this.Invalidate();
+ }
+ }
+
+ private STNodeCollection _Nodes;
+ ///
+ /// 获取画布中 Node 集合
+ ///
+ [Browsable(false)]
+ public STNodeCollection Nodes {
+ get {
+ return _Nodes;
+ }
+ }
+
+ private STNode _ActiveNode;
+ ///
+ /// 获取或设置当前画布中被选中的活动 Node
+ ///
+ [Browsable(false)]
+ public STNode ActiveNode {
+ get { return _ActiveNode; }
+ //set {
+ // if (value == _ActiveSelectedNode) return;
+ // if (_ActiveSelectedNode != null) _ActiveSelectedNode.OnLostFocus(new EventArgs());
+ // _ActiveSelectedNode = value;
+ // _ActiveSelectedNode.IsActive = true;
+ // this.Invalidate();
+ // this.OnSelectedChanged(new EventArgs());
+ //}
+ }
+
+ private STNode _HoverNode;
+ ///
+ /// 获取当前画布中鼠标悬停的 Node
+ ///
+ [Browsable(false)]
+ public STNode HoverNode {
+ get { return _HoverNode; }
+ }
+ //========================================color================================
+ private Color _GridColor = Color.Black;
+ ///
+ /// 获取或设置绘制画布背景时 网格线条颜色
+ ///
+ [Description("获取或设置绘制画布背景时 网格线条颜色"), DefaultValue(typeof(Color), "Black")]
+ public Color GridColor {
+ get { return _GridColor; }
+ set {
+ _GridColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _BorderColor = Color.Black;
+ ///
+ /// 获取或设置画布中 Node 边框颜色
+ ///
+ [Description("获取或设置画布中 Node 边框颜色"), DefaultValue(typeof(Color), "Black")]
+ public Color BorderColor {
+ get { return _BorderColor; }
+ set {
+ _BorderColor = value;
+ if (m_img_border != null) m_img_border.Dispose();
+ m_img_border = this.CreateBorderImage(value);
+ this.Invalidate();
+ }
+ }
+
+ private Color _BorderHoverColor = Color.Gray;
+ ///
+ /// 获取或设置画布中悬停 Node 边框颜色
+ ///
+ [Description("获取或设置画布中悬停 Node 边框颜色"), DefaultValue(typeof(Color), "Gray")]
+ public Color BorderHoverColor {
+ get { return _BorderHoverColor; }
+ set {
+ _BorderHoverColor = value;
+ if (m_img_border_hover != null) m_img_border_hover.Dispose();
+ m_img_border_hover = this.CreateBorderImage(value);
+ this.Invalidate();
+ }
+ }
+
+ private Color _BorderSelectColor = Color.Orange;
+ ///
+ /// 获取或设置画布中选中 Node 边框颜色
+ ///
+ [Description("获取或设置画布中选中 Node 边框颜色"), DefaultValue(typeof(Color), "Orange")]
+ public Color BorderSelectColor {
+ get { return _BorderSelectColor; }
+ set {
+ _BorderSelectColor = value;
+ if (m_img_border_selected != null) m_img_border_selected.Dispose();
+ m_img_border_selected = this.CreateBorderImage(value);
+ this.Invalidate();
+ }
+ }
+
+ private Color _BorderActiveColor = Color.OrangeRed;
+ ///
+ /// 获取或设置画布中活动 Node 边框颜色
+ ///
+ [Description("获取或设置画布中活动 Node 边框颜色"), DefaultValue(typeof(Color), "OrangeRed")]
+ public Color BorderActiveColor {
+ get { return _BorderActiveColor; }
+ set {
+ _BorderActiveColor = value;
+ if (m_img_border_active != null) m_img_border_active.Dispose();
+ m_img_border_active = this.CreateBorderImage(value);
+ this.Invalidate();
+ }
+ }
+
+ private Color _MarkForeColor = Color.White;
+ ///
+ /// 获取或设置画布绘制 Node 标记详情采用的前景色
+ ///
+ [Description("获取或设置画布绘制 Node 标记详情采用的前景色"), DefaultValue(typeof(Color), "White")]
+ public Color MarkForeColor {
+ get { return _MarkBackColor; }
+ set {
+ _MarkBackColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _MarkBackColor = Color.FromArgb(180, Color.Black);
+ ///
+ /// 获取或设置画布绘制 Node 标记详情采用的背景色
+ ///
+ [Description("获取或设置画布绘制 Node 标记详情采用的背景色"), DefaultValue(typeof(Color), "Black")]
+ public Color MarkBackColor {
+ get { return _MarkBackColor; }
+ set {
+ _MarkBackColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _MagnetLineColor = Color.Magenta;
+ ///
+ /// 获取或设置画布中移动 Node 时候 磁铁标记线颜色
+ ///
+ [Description("获取或设置画布中移动 Node 时候 磁铁标记线颜色"), DefaultValue(typeof(Color), "Magenta")]
+ public Color MagnetLineColor {
+ get { return _MagnetLineColor; }
+ set { _MagnetLineColor = value; }
+ }
+
+ private Color _SelectedRectangleColor = Color.DodgerBlue;
+ ///
+ /// 获取或设置画布中选择矩形区域的颜色
+ ///
+ [Description("获取或设置画布中选择矩形区域的颜色"), DefaultValue(typeof(Color), "DodgerBlue")]
+ public Color SelectedRectangleColor {
+ get { return _SelectedRectangleColor; }
+ set { _SelectedRectangleColor = value; }
+ }
+
+ private Color _HighLineColor = Color.Cyan;
+ ///
+ /// 获取或设置画布中高亮连线的颜色
+ ///
+ [Description("获取或设置画布中高亮连线的颜色"), DefaultValue(typeof(Color), "Cyan")]
+ public Color HighLineColor {
+ get { return _HighLineColor; }
+ set { _HighLineColor = value; }
+ }
+
+ private Color _LocationForeColor = Color.Red;
+ ///
+ /// 获取或设置画布中边缘位置提示区域前景色
+ ///
+ [Description("获取或设置画布中边缘位置提示区域前景色"), DefaultValue(typeof(Color), "Red")]
+ public Color LocationForeColor {
+ get { return _LocationForeColor; }
+ set {
+ _LocationForeColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _LocationBackColor = Color.FromArgb(120, Color.Black);
+ ///
+ /// 获取或设置画布中边缘位置提示区域背景色
+ ///
+ [Description("获取或设置画布中边缘位置提示区域背景色")]
+ public Color LocationBackColor {
+ get { return _LocationBackColor; }
+ set {
+ _LocationBackColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _UnknownTypeColor = Color.Gray;
+ ///
+ /// 获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色
+ ///
+ [Description("获取或设置画布中当 Node 中 Option 数据类型无法确定时应当使用的颜色"), DefaultValue(typeof(Color), "Gray")]
+ public Color UnknownTypeColor {
+ get { return _UnknownTypeColor; }
+ set {
+ _UnknownTypeColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Dictionary _TypeColor = new Dictionary();
+ ///
+ /// 获取或设置画布中 Node 中 Option 数据类型预设颜色
+ ///
+ [Browsable(false)]
+ public Dictionary TypeColor {
+ get { return _TypeColor; }
+ }
+
+ #endregion
+
+ #region protected properties ----------------------------------------------------------------------------------------
+ ///
+ /// 当前鼠标在控件中的实时位置
+ ///
+ protected Point m_pt_in_control;
+ ///
+ /// 当前鼠标在画布中的实时位置
+ ///
+ protected PointF m_pt_in_canvas;
+ ///
+ /// 鼠标点击时在控件上的位置
+ ///
+ protected Point m_pt_down_in_control;
+ ///
+ /// 鼠标点击时在画布中的位置
+ ///
+ protected PointF m_pt_down_in_canvas;
+ ///
+ /// 用于鼠标点击移动画布时候 鼠标点下时候的画布坐标位置
+ ///
+ protected PointF m_pt_canvas_old;
+ ///
+ /// 用于保存连线过程中保存点下 Option 的起点坐标
+ ///
+ protected Point m_pt_dot_down;
+ ///
+ /// 用于保存连线过程中鼠标点下的起点Option 当MouseUP时候 确定是否连接此节点
+ ///
+ protected STNodeOption m_option_down;
+
+ #endregion
+
+ public STNodeEditor() {
+ this.SetStyle(ControlStyles.UserPaint, true);
+ this.SetStyle(ControlStyles.ResizeRedraw, true);
+ this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
+ this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
+ this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
+ this._Nodes = new STNodeCollection(this);
+ this.BackColor = Color.FromArgb(255, 34, 34, 34);
+ this.Size = new Size(200, 200);
+ }
+
+ #region private fields --------------------------------------------------------------------------------------
+
+ private DrawingTools m_drawing_tools;
+ private NodeFindInfo m_find = new NodeFindInfo();
+ private MagnetInfo m_mi = new MagnetInfo();
+
+ private RectangleF m_rect_select = new RectangleF();
+ //节点边框预设图案
+ private Image m_img_border;
+ private Image m_img_border_hover;
+ private Image m_img_border_selected;
+ private Image m_img_border_active;
+ //用于鼠标滚动或者触摸板移动画布时候的动画效果 该值为需要移动到的真实坐标地址 查看->MoveCanvasThread()
+ private float m_real_canvas_x;
+ private float m_real_canvas_y;
+ //用于移动节点时候 保存鼠标点下时候选中的节点初始坐标
+ private Dictionary m_dic_pt_selected = new Dictionary();
+ //用于磁铁效果 移动节点时候 非选择节点的统计出来的需要参与磁铁效果的坐标 查看->BuildMagnetLocation()
+ private List m_lst_magnet_x = new List();
+ private List m_lst_magnet_y = new List();
+ //用于磁铁效果 移动节点时候 活动选择节点统计出来需要参与磁铁效果的坐标 查看->CheckMagnet()
+ private List m_lst_magnet_mx = new List();
+ private List m_lst_magnet_my = new List();
+ //用于鼠标滚动中计算时间触发间隔 根据间隔不同 画布产生的位移不同 查看->OnMouseWheel(),OnMouseHWheel()
+ private DateTime m_dt_vw = DateTime.Now;
+ private DateTime m_dt_hw = DateTime.Now;
+ //移动鼠标过程中的当前行为
+ private CanvasAction m_ca;
+ //保存已选中的节点
+ private HashSet m_hs_node_selected = new HashSet();
+
+ private bool m_is_process_mouse_event; //是否向下(Node or NodeControls)传递鼠标相关事件 如断开连接相关操作不应向下传递
+ private bool m_is_buildpath; //用于重绘过程中 判断该次是否要重新建立缓存连线的路径
+ private Pen m_p_line = new Pen(Color.Cyan, 2f); //用于绘制已经连接的线条
+ private Pen m_p_line_hover = new Pen(Color.Cyan, 4f); //用于绘制鼠标悬停时候的线条
+ private GraphicsPath m_gp_hover; //当前鼠标悬停的连线路径
+ private StringFormat m_sf = new StringFormat(); //文本格式 用于Mark绘制时候 设置文本格式
+ //保存每个连接线条与之对应的节点关系
+ private Dictionary m_dic_gp_info = new Dictionary();
+ //保存超出视觉区域的 Node 的位置
+ private List m_lst_node_out = new List();
+ //当前编辑器已加载的 Node 类型 用于从文件或者数据中加载节点使用
+ private Dictionary m_dic_type = new Dictionary();
+
+ private int m_time_alert;
+ private int m_alpha_alert;
+ private string m_str_alert;
+ private Color m_forecolor_alert;
+ private Color m_backcolor_alert;
+ private DateTime m_dt_alert;
+ private Rectangle m_rect_alert;
+ private AlertLocation m_al;
+
+ #endregion
+
+ #region event ----------------------------------------------------------------------------------------------------
+ ///
+ /// 选择的节点发生变化时候发生
+ ///
+ [Description("选择的节点发生变化时候发生")]
+ public event EventHandler SelectedChanged;
+ ///
+ /// 悬停的节点发生变化时候发生
+ ///
+ [Description("悬停的节点发生变化时候发生")]
+ public event EventHandler HoverChanged;
+ ///
+ /// 当节点被添加时候发生
+ ///
+ [Description("当节点被添加时候发生")]
+ public event STNodeEditorEventHandler NodeAdded;
+ ///
+ /// 当节点被移除时候发生
+ ///
+ [Description("当节点被移除时候发生")]
+ public event STNodeEditorEventHandler NodeRemoved;
+ ///
+ /// 移动画布原点时候发生
+ ///
+ [Description("移动画布原点时候发生")]
+ public event EventHandler CanvasMoved;
+ ///
+ /// 缩放画布时候发生
+ ///
+ [Description("缩放画布时候发生")]
+ public event EventHandler CanvasScaled;
+ ///
+ /// 连接节点选项时候发生
+ ///
+ [Description("连接节点选项时候发生")]
+ public event STNodeEditorOptionEventHandler OptionConnected;
+ ///
+ /// 正在连接节点选项时候发生
+ ///
+ [Description("正在连接节点选项时候发生")]
+ public event STNodeEditorOptionEventHandler OptionConnecting;
+ ///
+ /// 断开节点选项时候发生
+ ///
+ [Description("断开节点选项时候发生")]
+ public event STNodeEditorOptionEventHandler OptionDisConnected;
+ ///
+ /// 正在断开节点选项时候发生
+ ///
+ [Description("正在断开节点选项时候发生")]
+ public event STNodeEditorOptionEventHandler OptionDisConnecting;
+
+ protected virtual void OnSelectedChanged(EventArgs e) {
+ if (this.SelectedChanged != null) this.SelectedChanged(this, e);
+ }
+ protected virtual void OnHoverChanged(EventArgs e) {
+ if (this.HoverChanged != null) this.HoverChanged(this, e);
+ }
+ protected internal virtual void OnNodeAdded(STNodeEditorEventArgs e) {
+ if (this.NodeAdded != null) this.NodeAdded(this, e);
+ }
+ protected internal virtual void OnNodeRemoved(STNodeEditorEventArgs e) {
+ if (this.NodeRemoved != null) this.NodeRemoved(this, e);
+ }
+ protected virtual void OnCanvasMoved(EventArgs e) {
+ if (this.CanvasMoved != null) this.CanvasMoved(this, e);
+ }
+ protected virtual void OnCanvasScaled(EventArgs e) {
+ if (this.CanvasScaled != null) this.CanvasScaled(this, e);
+ }
+ protected internal virtual void OnOptionConnected(STNodeEditorOptionEventArgs e) {
+ if (this.OptionConnected != null) this.OptionConnected(this, e);
+ }
+ protected internal virtual void OnOptionDisConnected(STNodeEditorOptionEventArgs e) {
+ if (this.OptionDisConnected != null) this.OptionDisConnected(this, e);
+ }
+ protected internal virtual void OnOptionConnecting(STNodeEditorOptionEventArgs e) {
+ if (this.OptionConnecting != null) this.OptionConnecting(this, e);
+ }
+ protected internal virtual void OnOptionDisConnecting(STNodeEditorOptionEventArgs e) {
+ if (this.OptionDisConnecting != null) this.OptionDisConnecting(this, e);
+ }
+
+ #endregion event
+
+ #region override -----------------------------------------------------------------------------------------------------
+
+ protected override void OnCreateControl() {
+ m_drawing_tools = new DrawingTools() {
+ Pen = new Pen(Color.Black, 1),
+ SolidBrush = new SolidBrush(Color.Black)
+ };
+ m_img_border = this.CreateBorderImage(this._BorderColor);
+ m_img_border_active = this.CreateBorderImage(this._BorderActiveColor);
+ m_img_border_hover = this.CreateBorderImage(this._BorderHoverColor);
+ m_img_border_selected = this.CreateBorderImage(this._BorderSelectColor);
+ base.OnCreateControl();
+ new Thread(this.MoveCanvasThread) { IsBackground = true }.Start();
+ new Thread(this.ShowAlertThread) { IsBackground = true }.Start();
+ m_sf = new StringFormat();
+ m_sf.Alignment = StringAlignment.Near;
+ m_sf.FormatFlags = StringFormatFlags.NoWrap;
+ m_sf.SetTabStops(0, new float[] { 40 });
+ }
+
+ protected override void WndProc(ref Message m) {
+ base.WndProc(ref m);
+ Point pt = new Point(((int)m.LParam) >> 16, (ushort)m.LParam);
+ pt = base.PointToClient(pt);
+ if (m.Msg == WM_MOUSEHWHEEL) { //获取水平滚动消息
+ MouseButtons mb = MouseButtons.None;
+ int n = (ushort)m.WParam;
+ if ((n & 0x0001) == 0x0001) mb |= MouseButtons.Left;
+ if ((n & 0x0010) == 0x0010) mb |= MouseButtons.Middle;
+ if ((n & 0x0002) == 0x0002) mb |= MouseButtons.Right;
+ if ((n & 0x0020) == 0x0020) mb |= MouseButtons.XButton1;
+ if ((n & 0x0040) == 0x0040) mb |= MouseButtons.XButton2;
+ this.OnMouseHWheel(new MouseEventArgs(mb, 0, pt.X, pt.Y, ((int)m.WParam) >> 16));
+ }
+ }
+
+ protected override void OnPaint(PaintEventArgs e) {
+ base.OnPaint(e);
+ Graphics g = e.Graphics;
+ g.Clear(this.BackColor);
+ g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
+ m_drawing_tools.Graphics = g;
+ SolidBrush brush = m_drawing_tools.SolidBrush;
+
+ if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, this.Width, this.Height);
+
+ g.TranslateTransform(this._CanvasOffsetX, this._CanvasOffsetY); //移动坐标系
+ g.ScaleTransform(this._CanvasScale, this._CanvasScale); //缩放绘图表面
+
+ this.OnDrawNode(m_drawing_tools, this.ControlToCanvas(this.ClientRectangle));
+ this.OnDrawConnectedLine(m_drawing_tools);
+
+ if (m_ca == CanvasAction.ConnectOption) { //如果正在连线
+ m_drawing_tools.Pen.Color = this._HighLineColor;
+ if (m_option_down.IsInput)
+ this.DrawBezier(g, m_drawing_tools.Pen, m_pt_in_canvas, m_pt_dot_down, this._Curvature);
+ else
+ this.DrawBezier(g, m_drawing_tools.Pen, m_pt_dot_down, m_pt_in_canvas, this._Curvature);
+ }
+ //重置绘图坐标 我认为除了节点以外的其它 修饰相关的绘制不应该在Canvas坐标系中绘制 而应该使用控件的坐标进行绘制 不然会受到缩放比影响
+ g.ResetTransform();
+
+ switch (m_ca) {
+ case CanvasAction.MoveNode: //移动过程中 绘制对齐参考线
+ this.OnDrawMagnetLine(m_drawing_tools, m_mi);
+ break;
+ case CanvasAction.SelectRectangle: //绘制矩形选取
+ this.OnDrawSelectedRectangle(m_drawing_tools, this.CanvasToControl(m_rect_select));
+ break;
+ case CanvasAction.DrawMarkDetails: //绘制标记信息详情
+ if (!string.IsNullOrEmpty(m_find.Mark)) this.OnDrawMark(m_drawing_tools);
+ break;
+ }
+
+ if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, this.Size, m_lst_node_out);
+ this.OnDrawAlert(g);
+ }
+
+ protected override void OnMouseDown(MouseEventArgs e) {
+ base.OnMouseDown(e);
+ this.Focus();
+ m_ca = CanvasAction.None;
+ m_mi.XMatched = m_mi.YMatched = false;
+ m_pt_down_in_control = e.Location;
+ m_pt_down_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale);
+ m_pt_down_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale);
+ m_pt_canvas_old.X = this._CanvasOffsetX;
+ m_pt_canvas_old.Y = this._CanvasOffsetY;
+
+ if (m_gp_hover != null && e.Button == MouseButtons.Right) { //断开连接
+ this.DisConnectionHover();
+ m_is_process_mouse_event = false; //终止MouseClick与MouseUp向下传递
+ return;
+ }
+
+ NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_down_in_canvas);
+ if (!string.IsNullOrEmpty(nfi.Mark)) { //如果点下的是标记信息
+ m_ca = CanvasAction.DrawMarkDetails;
+ this.Invalidate();
+ return;
+ }
+
+ if (nfi.NodeOption != null) { //如果点下的Option的连接点
+ this.StartConnect(nfi.NodeOption);
+ return;
+ }
+
+ if (nfi.Node != null) {
+ nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta));
+ bool bCtrlDown = (Control.ModifierKeys & Keys.Control) == Keys.Control;
+ if (bCtrlDown) {
+ if (nfi.Node.IsSelected) {
+ nfi.Node.SetSelected(false, false);
+ m_hs_node_selected.Remove(nfi.Node);
+ if (nfi.Node == this._ActiveNode) {
+ this.SetActiveNode(null);
+ }
+ } else {
+ nfi.Node.SetSelected(true, false);
+ m_hs_node_selected.Add(nfi.Node);
+ }
+ this.Invalidate();
+ this.OnSelectedChanged(new EventArgs());
+ return;
+ } else if (!nfi.Node.IsSelected) {
+ foreach (var n in m_hs_node_selected) n.SetSelected(false, false);
+ m_hs_node_selected.Clear();
+ }
+ nfi.Node.SetSelected(true, false);
+ m_hs_node_selected.Add(nfi.Node); //添加到已选择节点
+ if (this.PointInRectangle(nfi.Node.TitleRectangle, m_pt_down_in_canvas.X, m_pt_down_in_canvas.Y)) {
+ if (e.Button == MouseButtons.Right) {
+ if (nfi.Node.ContextMenuStrip != null) {
+ nfi.Node.ContextMenuStrip.Show(this.PointToScreen(e.Location));
+ }
+ } else {
+ m_dic_pt_selected.Clear();
+ foreach (STNode n in m_hs_node_selected)//记录已选择节点位置 如果需要移动已选中节点时候 将会有用
+ m_dic_pt_selected.Add(n, n.Location);
+ m_ca = CanvasAction.MoveNode; //如果点下的是节点的标题 则可以移动该节点
+ if (this._Magnet) this.BuildMagnetLocation(); //建立磁铁需要的坐标 如果需要移动已选中节点时候 将会有用
+ }
+ }
+ //nfi.Node.OnMouseDown(new MouseEventArgs(e.Button, e.Clicks, (int)m_pt_down_in_canvas.X - nfi.Node.Left, (int)m_pt_down_in_canvas.Y - nfi.Node.Top, e.Delta));
+ } else {
+ foreach (var n in m_hs_node_selected) n.SetSelected(false, false);//没有点下任何东西 清空已经选择节点
+ m_ca = CanvasAction.SelectRectangle; //进入矩形区域选择模式
+ m_rect_select.Width = m_rect_select.Height = 0;
+ }
+ this.SetActiveNode(nfi.Node);
+ }
+
+ protected override void OnMouseMove(MouseEventArgs e) {
+ base.OnMouseMove(e);
+ m_pt_in_control = e.Location;
+ m_pt_in_canvas.X = ((e.X - this._CanvasOffsetX) / this._CanvasScale);
+ m_pt_in_canvas.Y = ((e.Y - this._CanvasOffsetY) / this._CanvasScale);
+
+ if (e.Button == MouseButtons.Middle) { //鼠标中键移动画布
+ this._CanvasOffsetX = m_real_canvas_x = m_pt_canvas_old.X + (e.X - m_pt_down_in_control.X);
+ this._CanvasOffsetY = m_real_canvas_y = m_pt_canvas_old.Y + (e.Y - m_pt_down_in_control.Y);
+ this.Invalidate();
+ return;
+ }
+ if (e.Button == MouseButtons.Left) { //如果鼠标左键点下 判断行为
+ m_gp_hover = null;
+ switch (m_ca) {
+ case CanvasAction.MoveNode: this.MoveNode(e.Location); return; //当前移动节点
+ case CanvasAction.ConnectOption: this.Invalidate(); return; //当前正在连线
+ case CanvasAction.SelectRectangle: //当前正在选取
+ m_rect_select.X = m_pt_down_in_canvas.X < m_pt_in_canvas.X ? m_pt_down_in_canvas.X : m_pt_in_canvas.X;
+ m_rect_select.Y = m_pt_down_in_canvas.Y < m_pt_in_canvas.Y ? m_pt_down_in_canvas.Y : m_pt_in_canvas.Y;
+ m_rect_select.Width = Math.Abs(m_pt_in_canvas.X - m_pt_down_in_canvas.X);
+ m_rect_select.Height = Math.Abs(m_pt_in_canvas.Y - m_pt_down_in_canvas.Y);
+ foreach (STNode n in this._Nodes) {
+ n.SetSelected(m_rect_select.IntersectsWith(n.Rectangle), false);
+ }
+ this.Invalidate();
+ return;
+ }
+ }
+ //若不存在行为 则判断鼠标下方是否存在其他对象
+ NodeFindInfo nfi = this.FindNodeFromPoint(m_pt_in_canvas);
+ bool bRedraw = false;
+ if (this._HoverNode != nfi.Node) { //鼠标悬停到Node上
+ if (nfi.Node != null) nfi.Node.OnMouseEnter(new EventArgs());
+ if (this._HoverNode != null)
+ this._HoverNode.OnMouseLeave(new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_in_canvas.X - this._HoverNode.Left,
+ (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta));
+ this._HoverNode = nfi.Node;
+ this.OnHoverChanged(new EventArgs());
+ bRedraw = true;
+ }
+ if (this._HoverNode != null)
+ this._HoverNode.OnMouseMove(new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_in_canvas.X - this._HoverNode.Left,
+ (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta));
+
+ GraphicsPath gp = null;
+ foreach (var v in m_dic_gp_info) { //判断鼠标是否悬停到连线路径上
+ if (v.Key.IsOutlineVisible(m_pt_in_canvas, m_p_line_hover)) {
+ gp = v.Key;
+ break;
+ }
+ }
+ if (m_gp_hover != gp) {
+ m_gp_hover = gp;
+ bRedraw = true;
+ }
+ if (bRedraw) this.Invalidate();
+ }
+
+ protected override void OnMouseUp(MouseEventArgs e) {
+ base.OnMouseUp(e);
+ var nfi = this.FindNodeFromPoint(m_pt_in_canvas);
+ switch (m_ca) { //鼠标抬起时候 判断行为
+ case CanvasAction.MoveNode: //若正在移动Node 则重新记录当前位置
+ foreach (STNode n in m_dic_pt_selected.Keys.ToList()) m_dic_pt_selected[n] = n.Location;
+ break;
+ case CanvasAction.ConnectOption: //若正在连线 则结束连接
+ if (e.Location == m_pt_down_in_control) break;
+ if (nfi.NodeOption != null) {
+ if (m_option_down.IsInput)
+ nfi.NodeOption.ConnectOption(m_option_down);
+ else
+ m_option_down.ConnectOption(nfi.NodeOption);
+ }
+ break;
+ case CanvasAction.SelectRectangle: //若正在进行选取 则判断是否有选中目标
+ m_hs_node_selected.Clear();
+ foreach (STNode n in this._Nodes) {
+ if (n.IsSelected) m_hs_node_selected.Add(n);
+ }
+ if (m_hs_node_selected.Count != 0) this.OnSelectedChanged(new EventArgs());
+ break;
+ }
+ if (m_is_process_mouse_event && this._ActiveNode != null) {
+ var mea = new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_in_canvas.X - this._ActiveNode.Left,
+ (int)m_pt_in_canvas.Y - this._ActiveNode.Top, e.Delta);
+ this._ActiveNode.OnMouseUp(mea);
+ }
+ m_is_process_mouse_event = true; //当前为断开连接操作不进行事件传递 下次将接受事件
+ m_ca = CanvasAction.None;
+ this.Invalidate();
+ }
+
+ protected override void OnMouseLeave(EventArgs e) {
+ base.OnMouseLeave(e);
+ if (this._HoverNode != null) this._HoverNode.OnMouseLeave(e);
+ this._HoverNode = null;
+ this.Invalidate();
+ }
+
+ protected override void OnMouseWheel(MouseEventArgs e) {
+ base.OnMouseWheel(e);
+ if ((Control.ModifierKeys & Keys.Control) == Keys.Control) {
+ float f = this._CanvasScale + (e.Delta < 0 ? -0.1f : 0.1f);
+ this.ScaleCanvas(f, this.Width / 2, this.Height / 2);
+ } else {
+ var nfi = this.FindNodeFromPoint(m_pt_in_canvas);
+ if (this._HoverNode != null) {
+ this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_in_canvas.X - this._HoverNode.Left,
+ (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta));
+ return;
+ }
+ int t = (int)DateTime.Now.Subtract(m_dt_vw).TotalMilliseconds;
+ if (t <= 30) t = 40;
+ else if (t <= 100) t = 20;
+ else if (t <= 150) t = 10;
+ else if (t <= 300) t = 4;
+ else t = 2;
+ this.MoveCanvas(this._CanvasOffsetX, m_real_canvas_y + (e.Delta < 0 ? -t : t), true, CanvasMoveArgs.Top);//process mouse mid
+ m_dt_vw = DateTime.Now;
+ }
+ }
+
+ protected virtual void OnMouseHWheel(MouseEventArgs e) {
+ if ((Control.ModifierKeys & Keys.Control) == Keys.Control) return;
+ if (this._HoverNode != null) {
+ this._HoverNode.OnMouseWheel(new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_in_canvas.X - this._HoverNode.Left,
+ (int)m_pt_in_canvas.Y - this._HoverNode.Top, e.Delta));
+ return;
+ }
+ int t = (int)DateTime.Now.Subtract(m_dt_hw).TotalMilliseconds;
+ if (t <= 30) t = 40;
+ else if (t <= 100) t = 20;
+ else if (t <= 150) t = 10;
+ else if (t <= 300) t = 4;
+ else t = 2;
+ this.MoveCanvas(m_real_canvas_x + (e.Delta > 0 ? -t : t), this._CanvasOffsetY, true, CanvasMoveArgs.Left);
+ m_dt_hw = DateTime.Now;
+ }
+ //===========================for node other event==================================
+ protected override void OnMouseClick(MouseEventArgs e) {
+ base.OnMouseClick(e);
+ if (this._ActiveNode != null && m_is_process_mouse_event) {
+ if (!this.PointInRectangle(this._ActiveNode.Rectangle, m_pt_in_canvas.X, m_pt_in_canvas.Y)) return;
+ this._ActiveNode.OnMouseClick(new MouseEventArgs(e.Button, e.Clicks,
+ (int)m_pt_down_in_canvas.X - this._ActiveNode.Left,
+ (int)m_pt_down_in_canvas.Y - this._ActiveNode.Top, e.Delta));
+ }
+ }
+
+ protected override void OnKeyDown(KeyEventArgs e) {
+ base.OnKeyDown(e);
+ if (this._ActiveNode != null) this._ActiveNode.OnKeyDown(e);
+ }
+
+ protected override void OnKeyUp(KeyEventArgs e) {
+ base.OnKeyUp(e);
+ if (this._ActiveNode != null) this._ActiveNode.OnKeyUp(e);
+ }
+
+ protected override void OnKeyPress(KeyPressEventArgs e) {
+ base.OnKeyPress(e);
+ if (this._ActiveNode != null) this._ActiveNode.OnKeyPress(e);
+ }
+
+ #endregion
+
+ #region protected ----------------------------------------------------------------------------------------------------
+ ///
+ /// 当绘制背景网格线时候发生
+ ///
+ /// 绘制工具
+ /// 需要绘制宽度
+ /// 需要绘制高度
+ protected virtual void OnDrawGrid(DrawingTools dt, int nWidth, int nHeight) {
+ Graphics g = dt.Graphics;
+ using (Pen p_2 = new Pen(Color.FromArgb(65, this._GridColor))) {
+ using (Pen p_1 = new Pen(Color.FromArgb(30, this._GridColor))) {
+ float nIncrement = (20 * this._CanvasScale); //网格间的间隔 根据比例绘制
+ int n = 5 - (int)(this._CanvasOffsetX / nIncrement);
+ for (float f = this._CanvasOffsetX % nIncrement; f < nWidth; f += nIncrement)
+ g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), f, 0, f, nHeight);
+ n = 5 - (int)(this._CanvasOffsetY / nIncrement);
+ for (float f = this._CanvasOffsetY % nIncrement; f < nHeight; f += nIncrement)
+ g.DrawLine((n++ % 5 == 0 ? p_2 : p_1), 0, f, nWidth, f);
+ //原点两天线
+ p_1.Color = Color.FromArgb(this._Nodes.Count == 0 ? 255 : 120, this._GridColor);
+ g.DrawLine(p_1, this._CanvasOffsetX, 0, this._CanvasOffsetX, nHeight);
+ g.DrawLine(p_1, 0, this._CanvasOffsetY, nWidth, this._CanvasOffsetY);
+ }
+ }
+ }
+ ///
+ /// 当绘制 Node 时候发生
+ ///
+ /// 绘制工具
+ /// 可视画布区域大小
+ protected virtual void OnDrawNode(DrawingTools dt, Rectangle rect) {
+ m_lst_node_out.Clear(); //清空超出视觉区域的 Node 的坐标
+ //var rect_canvas_display = this.ControlToCanvas(rect);
+ Image img_border = null;
+ foreach (STNode n in this._Nodes) {
+ n.CheckSize(dt);
+ img_border = m_img_border;
+ if (this._ShowBorder) { //如果绘制边框 判断状态
+ if (this._ActiveNode == n) img_border = m_img_border_active;
+ else if (/*m_hs_node_selected.Contains(n)*/n.IsSelected) img_border = m_img_border_selected;
+ else if (this._HoverNode == n) img_border = m_img_border_hover;
+ this.RenderBorder(dt.Graphics, n.Rectangle, img_border);
+ if (!string.IsNullOrEmpty(n.Mark)) this.RenderBorder(dt.Graphics, n.MarkRectangle, img_border);
+ }
+ n.OnDrawNode(dt); //调用 Node 进行自身绘制主体部分
+ if (!string.IsNullOrEmpty(n.Mark)) n.OnDrawMark(dt); //调用 Node 进行自身绘制 Mark 区域
+ if (!rect.IntersectsWith(n.Rectangle)) {
+ m_lst_node_out.Add(n.Location); //判断此 Node 是否超出视觉区域
+ }
+ }
+ }
+ ///
+ /// 当绘制已连接路径时候发生
+ ///
+ /// 绘制工具
+ protected virtual void OnDrawConnectedLine(DrawingTools dt) {
+ Graphics g = dt.Graphics;
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ m_p_line_hover.Color = Color.FromArgb(50, 0, 0, 0);
+ var t = typeof(object);
+ foreach (STNode n in this._Nodes) {
+ foreach (STNodeOption op in n.OutputOptions) {
+ if (op.DotColor != Color.Transparent) //确定线条颜色
+ m_p_line.Color = op.DotColor;
+ else {
+ if (op.DataType == t)
+ m_p_line.Color = this._UnknownTypeColor;
+ else
+ m_p_line.Color = this._TypeColor.ContainsKey(op.DataType) ? this._TypeColor[op.DataType] : this._UnknownTypeColor;//value can not be null
+ }
+ foreach (var v in op.ConnectedOption) {
+ this.DrawBezier(g, m_p_line_hover, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2,
+ v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature);
+ this.DrawBezier(g, m_p_line, op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2,
+ v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature);
+ if (m_is_buildpath) { //如果当前绘制需要重新建立已连接的路径缓存
+ GraphicsPath gp = this.CreateBezierPath(op.DotLeft + op.DotSize, op.DotTop + op.DotSize / 2,
+ v.DotLeft - 1, v.DotTop + v.DotSize / 2, this._Curvature);
+ m_dic_gp_info.Add(gp, new ConnectionInfo() { Output = op, Input = v });
+ }
+ }
+ }
+ }
+ m_p_line_hover.Color = this._HighLineColor;
+ if (m_gp_hover != null) { //如果当前有被悬停的连接路劲 则高亮绘制
+ g.DrawPath(m_p_line_hover, m_gp_hover);
+ }
+ m_is_buildpath = false; //重置标志 下次绘制时候 不再重新建立路径缓存
+ }
+ ///
+ /// 当绘制 Mark 详情信息时候发生
+ ///
+ /// 绘制工具
+ protected virtual void OnDrawMark(DrawingTools dt) {
+ Graphics g = dt.Graphics;
+ SizeF sz = g.MeasureString(m_find.Mark, this.Font); //确认文字需要的大小
+ Rectangle rect = new Rectangle(m_pt_in_control.X + 15,
+ m_pt_in_control.Y + 10,
+ (int)sz.Width + 6,
+ 4 + (this.Font.Height + 4) * m_find.MarkLines.Length); //sz.Height并没有考虑文字的行距 所以这里高度自己计算
+
+ if (rect.Right > this.Width) rect.X = this.Width - rect.Width;
+ if (rect.Bottom > this.Height) rect.Y = this.Height - rect.Height;
+ if (rect.X < 0) rect.X = 0;
+ if (rect.Y < 0) rect.Y = 0;
+
+ dt.SolidBrush.Color = this._MarkBackColor;
+ g.SmoothingMode = SmoothingMode.None;
+ g.FillRectangle(dt.SolidBrush, rect); //绘制背景区域
+ rect.Width--; rect.Height--;
+ dt.Pen.Color = Color.FromArgb(255, this._MarkBackColor);
+ g.DrawRectangle(dt.Pen, rect);
+ dt.SolidBrush.Color = this._MarkForeColor;
+
+ m_sf.LineAlignment = StringAlignment.Center;
+ //g.SmoothingMode = SmoothingMode.HighQuality;
+ rect.X += 2; rect.Width -= 3;
+ rect.Height = this.Font.Height + 4;
+ int nY = rect.Y + 2;
+ for (int i = 0; i < m_find.MarkLines.Length; i++) { //绘制文字
+ rect.Y = nY + i * (this.Font.Height + 4);
+ g.DrawString(m_find.MarkLines[i], this.Font, dt.SolidBrush, rect, m_sf);
+ }
+ }
+ ///
+ /// 当移动 Node 时候 需要显示对齐参考线时候发生
+ ///
+ /// 绘制工具
+ /// 匹配的磁铁信息
+ protected virtual void OnDrawMagnetLine(DrawingTools dt, MagnetInfo mi) {
+ Graphics g = dt.Graphics;
+ Pen pen = m_drawing_tools.Pen;
+ pen.Color = this._MagnetLineColor;
+ if (mi.XMatched) g.DrawLine(pen, this.CanvasToControl(mi.X, true), 0, this.CanvasToControl(mi.X, true), this.Height);
+ if (mi.YMatched) g.DrawLine(pen, 0, this.CanvasToControl(mi.Y, false), this.Width, this.CanvasToControl(mi.Y, false));
+ }
+ ///
+ /// 绘制选择的矩形区域
+ ///
+ /// 绘制工具
+ /// 位于控件上的矩形区域
+ protected virtual void OnDrawSelectedRectangle(DrawingTools dt, RectangleF rectf) {
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ g.DrawRectangle(Pens.DodgerBlue, rectf.Left, rectf.Y, rectf.Width, rectf.Height);
+ brush.Color = Color.FromArgb(50, Color.DodgerBlue);
+ g.FillRectangle(brush, this.CanvasToControl(m_rect_select));
+ }
+ ///
+ /// 绘制超出视觉区域的 Node 位置提示信息
+ ///
+ /// 绘制工具
+ /// 提示框边距
+ /// 超出视觉区域的 Node 位置信息
+ protected virtual void OnDrawNodeOutLocation(DrawingTools dt, Size sz, List lstPts) {
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ brush.Color = this._LocationBackColor;
+ g.SmoothingMode = SmoothingMode.None;
+ if (lstPts.Count == this._Nodes.Count && this._Nodes.Count != 0) { //如果超出个数和集合个数一样多 则全部超出 绘制外切矩形
+ g.FillRectangle(brush, this.CanvasToControl(this._CanvasValidBounds));
+ }
+ g.FillRectangle(brush, 0, 0, 4, sz.Height); //绘制四边背景
+ g.FillRectangle(brush, sz.Width - 4, 0, 4, sz.Height);
+ g.FillRectangle(brush, 4, 0, sz.Width - 8, 4);
+ g.FillRectangle(brush, 4, sz.Height - 4, sz.Width - 8, 4);
+ brush.Color = this._LocationForeColor;
+ foreach (var v in lstPts) { //绘制点
+ var pt = this.CanvasToControl(v);
+ if (pt.X < 0) pt.X = 0;
+ if (pt.Y < 0) pt.Y = 0;
+ if (pt.X > sz.Width) pt.X = sz.Width - 4;
+ if (pt.Y > sz.Height) pt.Y = sz.Height - 4;
+ g.FillRectangle(brush, pt.X, pt.Y, 4, 4);
+ }
+ }
+ ///
+ /// 绘制提示信息
+ ///
+ /// 绘制工具
+ /// 需要绘制区域
+ /// 需要绘制文本
+ /// 信息前景色
+ /// 信息背景色
+ /// 信息位置
+ protected virtual void OnDrawAlert(DrawingTools dt, Rectangle rect, string strText, Color foreColor, Color backColor, AlertLocation al) {
+ if (m_alpha_alert == 0) return;
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+
+ g.SmoothingMode = SmoothingMode.None;
+ brush.Color = backColor;
+ dt.Pen.Color = brush.Color;
+ g.FillRectangle(brush, rect);
+ g.DrawRectangle(dt.Pen, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1);
+
+ brush.Color = foreColor;
+ m_sf.Alignment = StringAlignment.Center;
+ m_sf.LineAlignment = StringAlignment.Center;
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ g.DrawString(strText, this.Font, brush, rect, m_sf);
+ }
+ ///
+ /// 获取提示信息需要绘制的矩形区域
+ ///
+ /// 绘图表面
+ /// 需要绘制文本
+ /// 信息位置
+ /// 矩形区域
+ protected virtual Rectangle GetAlertRectangle(Graphics g, string strText, AlertLocation al) {
+ SizeF szf = g.MeasureString(m_str_alert, this.Font);
+ Size sz = new Size((int)Math.Round(szf.Width + 10), (int)Math.Round(szf.Height + 4));
+ Rectangle rect = new Rectangle(4, this.Height - sz.Height - 4, sz.Width, sz.Height);
+
+ switch (al) {
+ case AlertLocation.Left:
+ rect.Y = (this.Height - sz.Height) >> 1;
+ break;
+ case AlertLocation.Top:
+ rect.Y = 4;
+ rect.X = (this.Width - sz.Width) >> 1;
+ break;
+ case AlertLocation.Right:
+ rect.X = this.Width - sz.Width - 4;
+ rect.Y = (this.Height - sz.Height) >> 1;
+ break;
+ case AlertLocation.Bottom:
+ rect.X = (this.Width - sz.Width) >> 1;
+ break;
+ case AlertLocation.Center:
+ rect.X = (this.Width - sz.Width) >> 1;
+ rect.Y = (this.Height - sz.Height) >> 1;
+ break;
+ case AlertLocation.LeftTop:
+ rect.X = rect.Y = 4;
+ break;
+ case AlertLocation.RightTop:
+ rect.Y = 4;
+ rect.X = this.Width - sz.Width - 4;
+ break;
+ case AlertLocation.RightBottom:
+ rect.X = this.Width - sz.Width - 4;
+ break;
+ }
+ return rect;
+ }
+
+ #endregion protected
+
+ #region internal
+
+ internal void BuildLinePath() {
+ foreach (var v in m_dic_gp_info) v.Key.Dispose();
+ m_dic_gp_info.Clear();
+ m_is_buildpath = true;
+ this.Invalidate();
+ }
+
+ internal void OnDrawAlert(Graphics g) {
+ m_rect_alert = this.GetAlertRectangle(g, m_str_alert, m_al);
+ Color clr_fore = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_forecolor_alert.A), m_forecolor_alert);
+ Color clr_back = Color.FromArgb((int)((float)m_alpha_alert / 255 * m_backcolor_alert.A), m_backcolor_alert);
+ this.OnDrawAlert(m_drawing_tools, m_rect_alert, m_str_alert, clr_fore, clr_back, m_al);
+ }
+
+ #endregion internal
+
+ #region private -----------------------------------------------------------------------------------------------------
+
+ private void MoveCanvasThread() {
+ bool bRedraw;
+ while (true) {
+ bRedraw = false;
+ if (m_real_canvas_x != this._CanvasOffsetX) {
+ float nx = m_real_canvas_x - this._CanvasOffsetX;
+ float n = Math.Abs(nx) / 10;
+ float nTemp = Math.Abs(nx);
+ if (nTemp <= 4) n = 1;
+ else if (nTemp <= 12) n = 2;
+ else if (nTemp <= 30) n = 3;
+ if (nTemp < 1) this._CanvasOffsetX = m_real_canvas_x;
+ else
+ this._CanvasOffsetX += nx > 0 ? n : -n;
+ bRedraw = true;
+ }
+ if (m_real_canvas_y != this._CanvasOffsetY) {
+ float ny = m_real_canvas_y - this._CanvasOffsetY;
+ float n = Math.Abs(ny) / 10;
+ float nTemp = Math.Abs(ny);
+ if (nTemp <= 4) n = 1;
+ else if (nTemp <= 12) n = 2;
+ else if (nTemp <= 30) n = 3;
+ if (nTemp < 1)
+ this._CanvasOffsetY = m_real_canvas_y;
+ else
+ this._CanvasOffsetY += ny > 0 ? n : -n;
+ bRedraw = true;
+ }
+ if (bRedraw) {
+ m_pt_canvas_old.X = this._CanvasOffsetX;
+ m_pt_canvas_old.Y = this._CanvasOffsetY;
+ this.Invalidate();
+ Thread.Sleep(30);
+ } else {
+ Thread.Sleep(100);
+ }
+ }
+ }
+
+ private void ShowAlertThread() {
+ while (true) {
+ int nTime = m_time_alert - (int)DateTime.Now.Subtract(m_dt_alert).TotalMilliseconds;
+ if (nTime > 0) {
+ Thread.Sleep(nTime);
+ continue;
+ }
+ if (nTime < -1000) {
+ if (m_alpha_alert != 0) {
+ m_alpha_alert = 0;
+ this.Invalidate();
+ }
+ Thread.Sleep(100);
+ } else {
+ m_alpha_alert = (int)(255 - (-nTime / 1000F) * 255);
+ this.Invalidate(m_rect_alert);
+ Thread.Sleep(50);
+ }
+ }
+ }
+
+ private Image CreateBorderImage(Color clr) {
+ Image img = new Bitmap(12, 12);
+ using (Graphics g = Graphics.FromImage(img)) {
+ g.SmoothingMode = SmoothingMode.HighQuality;
+ using (GraphicsPath gp = new GraphicsPath()) {
+ gp.AddEllipse(new Rectangle(0, 0, 11, 11));
+ using (PathGradientBrush b = new PathGradientBrush(gp)) {
+ b.CenterColor = Color.FromArgb(200, clr);
+ b.SurroundColors = new Color[] { Color.FromArgb(10, clr) };
+ g.FillPath(b, gp);
+ }
+ }
+ }
+ return img;
+ }
+
+ private ConnectionStatus DisConnectionHover() {
+ if (!m_dic_gp_info.ContainsKey(m_gp_hover)) return ConnectionStatus.DisConnected;
+ ConnectionInfo ci = m_dic_gp_info[m_gp_hover];
+ var ret = ci.Output.DisConnectOption(ci.Input);
+ //this.OnOptionDisConnected(new STNodeOptionEventArgs(ci.Output, ci.Input, ret));
+ if (ret == ConnectionStatus.DisConnected) {
+ m_dic_gp_info.Remove(m_gp_hover);
+ m_gp_hover.Dispose();
+ m_gp_hover = null;
+ this.Invalidate();
+ }
+ return ret;
+ }
+
+ private void StartConnect(STNodeOption op) {
+ if (op.IsInput) {
+ m_pt_dot_down.X = op.DotLeft;
+ m_pt_dot_down.Y = op.DotTop + 5;
+ } else {
+ m_pt_dot_down.X = op.DotLeft + op.DotSize;
+ m_pt_dot_down.Y = op.DotTop + 5;
+ }
+ m_ca = CanvasAction.ConnectOption;
+ m_option_down = op;
+ }
+
+ private void MoveNode(Point pt) {
+ int nX = (int)((pt.X - m_pt_down_in_control.X) / this._CanvasScale);
+ int nY = (int)((pt.Y - m_pt_down_in_control.Y) / this._CanvasScale);
+ foreach (STNode v in m_hs_node_selected) {
+ v.Left = m_dic_pt_selected[v].X + nX;
+ v.Top = m_dic_pt_selected[v].Y + nY;
+ }
+ if (this._Magnet) {
+ MagnetInfo mi = this.CheckMagnet(this._ActiveNode);
+ if (mi.XMatched) {
+ foreach (STNode v in m_hs_node_selected) v.Left -= mi.OffsetX;
+ }
+ if (mi.YMatched) {
+ foreach (STNode v in m_hs_node_selected) v.Top -= mi.OffsetY;
+ }
+ }
+ this.Invalidate();
+ }
+
+ protected internal virtual void BuildBounds() {
+ if (this._Nodes.Count == 0) {
+ this._CanvasValidBounds = this.ControlToCanvas(this.DisplayRectangle);
+ return;
+ }
+ int x = int.MaxValue;
+ int y = int.MaxValue;
+ int r = int.MinValue;
+ int b = int.MinValue;
+ foreach (STNode n in this._Nodes) {
+ if (x > n.Left) x = n.Left;
+ if (y > n.Top) y = n.Top;
+ if (r < n.Right) r = n.Right;
+ if (b < n.Bottom) b = n.Bottom;
+ }
+ this._CanvasValidBounds.X = x - 60;
+ this._CanvasValidBounds.Y = y - 60;
+ this._CanvasValidBounds.Width = r - x + 120;
+ this._CanvasValidBounds.Height = b - y + 120;
+ }
+
+ private bool PointInRectangle(Rectangle rect, float x, float y) {
+ if (x < rect.Left) return false;
+ if (x > rect.Right) return false;
+ if (y < rect.Top) return false;
+ if (y > rect.Bottom) return false;
+ return true;
+ }
+
+ private void BuildMagnetLocation() {
+ m_lst_magnet_x.Clear();
+ m_lst_magnet_y.Clear();
+ foreach (STNode v in this._Nodes) {
+ if (v.IsSelected) continue;
+ m_lst_magnet_x.Add(v.Left);
+ m_lst_magnet_x.Add(v.Left + v.Width / 2);
+ m_lst_magnet_x.Add(v.Left + v.Width);
+ m_lst_magnet_y.Add(v.Top);
+ m_lst_magnet_y.Add(v.Top + v.Height / 2);
+ m_lst_magnet_y.Add(v.Top + v.Height);
+ }
+ }
+
+ private MagnetInfo CheckMagnet(STNode node) {
+ m_mi.XMatched = m_mi.YMatched = false;
+ m_lst_magnet_mx.Clear();
+ m_lst_magnet_my.Clear();
+ m_lst_magnet_mx.Add(node.Left + node.Width / 2);
+ m_lst_magnet_mx.Add(node.Left);
+ m_lst_magnet_mx.Add(node.Left + node.Width);
+ m_lst_magnet_my.Add(node.Top + node.Height / 2);
+ m_lst_magnet_my.Add(node.Top);
+ m_lst_magnet_my.Add(node.Top + node.Height);
+
+ bool bFlag = false;
+ foreach (var mx in m_lst_magnet_mx) {
+ foreach (var x in m_lst_magnet_x) {
+ if (Math.Abs(mx - x) <= 5) {
+ bFlag = true;
+ m_mi.X = x;
+ m_mi.OffsetX = mx - x;
+ m_mi.XMatched = true;
+ break;
+ }
+ }
+ if (bFlag) break;
+ }
+ bFlag = false;
+ foreach (var my in m_lst_magnet_my) {
+ foreach (var y in m_lst_magnet_y) {
+ if (Math.Abs(my - y) <= 5) {
+ bFlag = true;
+ m_mi.Y = y;
+ m_mi.OffsetY = my - y;
+ m_mi.YMatched = true;
+ break;
+ }
+ }
+ if (bFlag) break;
+ }
+ return m_mi;
+ }
+
+ private void DrawBezier(Graphics g, Pen p, PointF ptStart, PointF ptEnd, float f) {
+ this.DrawBezier(g, p, ptStart.X, ptStart.Y, ptEnd.X, ptEnd.Y, f);
+ }
+
+ private void DrawBezier(Graphics g, Pen p, float x1, float y1, float x2, float y2, float f) {
+ float n = (Math.Abs(x1 - x2) * f);
+ if (this._Curvature != 0 && n < 30) n = 30;
+ g.DrawBezier(p,
+ x1, y1,
+ x1 + n, y1,
+ x2 - n, y2,
+ x2, y2);
+ }
+
+ private GraphicsPath CreateBezierPath(float x1, float y1, float x2, float y2, float f) {
+ GraphicsPath gp = new GraphicsPath();
+ float n = (Math.Abs(x1 - x2) * f);
+ if (this._Curvature != 0 && n < 30) n = 30;
+ gp.AddBezier(
+ x1, y1,
+ x1 + n, y1,
+ x2 - n, y2,
+ x2, y2
+ );
+ return gp;
+ }
+
+ private void RenderBorder(Graphics g, Rectangle rect, Image img) {
+ //填充四个角
+ g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y - 5, 5, 5),
+ new Rectangle(0, 0, 5, 5), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.Right, rect.Y - 5, 5, 5),
+ new Rectangle(img.Width - 5, 0, 5, 5), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.X - 5, rect.Bottom, 5, 5),
+ new Rectangle(0, img.Height - 5, 5, 5), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.Right, rect.Bottom, 5, 5),
+ new Rectangle(img.Width - 5, img.Height - 5, 5, 5), GraphicsUnit.Pixel);
+ //四边
+ g.DrawImage(img, new Rectangle(rect.X - 5, rect.Y, 5, rect.Height),
+ new Rectangle(0, 5, 5, img.Height - 10), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.X, rect.Y - 5, rect.Width, 5),
+ new Rectangle(5, 0, img.Width - 10, 5), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.Right, rect.Y, 5, rect.Height),
+ new Rectangle(img.Width - 5, 5, 5, img.Height - 10), GraphicsUnit.Pixel);
+ g.DrawImage(img, new Rectangle(rect.X, rect.Bottom, rect.Width, 5),
+ new Rectangle(5, img.Height - 5, img.Width - 10, 5), GraphicsUnit.Pixel);
+ }
+
+ #endregion private
+
+ #region public --------------------------------------------------------------------------------------------------------
+ ///
+ /// 通过画布坐标进行寻找
+ ///
+ /// 画布中的坐标
+ /// 寻找到的数据
+ public NodeFindInfo FindNodeFromPoint(PointF pt) {
+ m_find.Node = null; m_find.NodeOption = null; m_find.Mark = null;
+ for (int i = this._Nodes.Count - 1; i >= 0; i--) {
+ if (!string.IsNullOrEmpty(this._Nodes[i].Mark) && this.PointInRectangle(this._Nodes[i].MarkRectangle, pt.X, pt.Y)) {
+ m_find.Mark = this._Nodes[i].Mark;
+ m_find.MarkLines = this._Nodes[i].MarkLines;
+ return m_find;
+ }
+ foreach (STNodeOption v in this._Nodes[i].InputOptions) {
+ if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v;
+ }
+ foreach (STNodeOption v in this._Nodes[i].OutputOptions) {
+ if (this.PointInRectangle(v.DotRectangle, pt.X, pt.Y)) m_find.NodeOption = v;
+ }
+ if (this.PointInRectangle(this._Nodes[i].Rectangle, pt.X, pt.Y)) {
+ m_find.Node = this._Nodes[i];
+ }
+ if (m_find.NodeOption != null || m_find.Node != null) return m_find;
+ }
+ return m_find;
+ }
+ ///
+ /// 获取已经被选择的 Node 集合
+ ///
+ /// Node 集合
+ public STNode[] GetSelectedNode() {
+ return m_hs_node_selected.ToArray();
+ }
+ ///
+ /// 将画布坐标转换为控件坐标
+ ///
+ /// 参数
+ /// 是否为 X 坐标
+ /// 转换后的坐标
+ public float CanvasToControl(float number, bool isX) {
+ return (number * this._CanvasScale) + (isX ? this._CanvasOffsetX : this._CanvasOffsetY);
+ }
+ ///
+ /// 将画布坐标转换为控件坐标
+ ///
+ /// 坐标
+ /// 转换后的坐标
+ public PointF CanvasToControl(PointF pt) {
+ pt.X = (pt.X * this._CanvasScale) + this._CanvasOffsetX;
+ pt.Y = (pt.Y * this._CanvasScale) + this._CanvasOffsetY;
+ return pt;
+ }
+ ///
+ /// 将画布坐标转换为控件坐标
+ ///
+ /// 坐标
+ /// 转换后的坐标
+ public Point CanvasToControl(Point pt) {
+ pt.X = (int)(pt.X * this._CanvasScale + this._CanvasOffsetX);
+ pt.Y = (int)(pt.Y * this._CanvasScale + this._CanvasOffsetY);
+ return pt;
+ }
+ ///
+ /// 将画布坐标转换为控件坐标
+ ///
+ /// 矩形区域
+ /// 转换后的矩形区域
+ public Rectangle CanvasToControl(Rectangle rect) {
+ rect.X = (int)((rect.X * this._CanvasScale) + this._CanvasOffsetX);
+ rect.Y = (int)((rect.Y * this._CanvasScale) + this._CanvasOffsetY);
+ rect.Width = (int)(rect.Width * this._CanvasScale);
+ rect.Height = (int)(rect.Height * this._CanvasScale);
+ return rect;
+ }
+ ///
+ /// 将画布坐标转换为控件坐标
+ ///
+ /// 矩形区域
+ /// 转换后的矩形区域
+ public RectangleF CanvasToControl(RectangleF rect) {
+ rect.X = (rect.X * this._CanvasScale) + this._CanvasOffsetX;
+ rect.Y = (rect.Y * this._CanvasScale) + this._CanvasOffsetY;
+ rect.Width = (rect.Width * this._CanvasScale);
+ rect.Height = (rect.Height * this._CanvasScale);
+ return rect;
+ }
+ ///
+ /// 将控件坐标转换为画布坐标
+ ///
+ /// 参数
+ /// 是否为 X 坐标
+ /// 转换后的坐标
+ public float ControlToCanvas(float number, bool isX) {
+ return (number - (isX ? this._CanvasOffsetX : this._CanvasOffsetY)) / this._CanvasScale;
+ }
+ ///
+ /// 将控件坐标转换为画布坐标
+ ///
+ /// 坐标
+ /// 转换后的坐标
+ public Point ControlToCanvas(Point pt) {
+ pt.X = (int)((pt.X - this._CanvasOffsetX) / this._CanvasScale);
+ pt.Y = (int)((pt.Y - this._CanvasOffsetY) / this._CanvasScale);
+ return pt;
+ }
+ ///
+ /// 将控件坐标转换为画布坐标
+ ///
+ /// 坐标
+ /// 转换后的坐标
+ public PointF ControlToCanvas(PointF pt) {
+ pt.X = ((pt.X - this._CanvasOffsetX) / this._CanvasScale);
+ pt.Y = ((pt.Y - this._CanvasOffsetY) / this._CanvasScale);
+ return pt;
+ }
+ ///
+ /// 将控件坐标转换为画布坐标
+ ///
+ /// 矩形区域
+ /// 转换后的区域
+ public Rectangle ControlToCanvas(Rectangle rect) {
+ rect.X = (int)((rect.X - this._CanvasOffsetX) / this._CanvasScale);
+ rect.Y = (int)((rect.Y - this._CanvasOffsetY) / this._CanvasScale);
+ rect.Width = (int)(rect.Width / this._CanvasScale);
+ rect.Height = (int)(rect.Height / this._CanvasScale);
+ return rect;
+ }
+ ///
+ /// 将控件坐标转换为画布坐标
+ ///
+ /// 矩形区域
+ /// 转换后的区域
+ public RectangleF ControlToCanvas(RectangleF rect) {
+ rect.X = ((rect.X - this._CanvasOffsetX) / this._CanvasScale);
+ rect.Y = ((rect.Y - this._CanvasOffsetY) / this._CanvasScale);
+ rect.Width = (rect.Width / this._CanvasScale);
+ rect.Height = (rect.Height / this._CanvasScale);
+ return rect;
+ }
+ ///
+ /// 移动画布原点坐标到指定的控件坐标位置
+ /// 当不存在 Node 时候 无法移动
+ ///
+ /// X 坐标
+ /// Y 坐标
+ /// 移动过程中是否启动动画效果
+ /// 指定需要修改的坐标参数
+ public void MoveCanvas(float x, float y, bool bAnimation, CanvasMoveArgs ma) {
+ if (this._Nodes.Count == 0) {
+ m_real_canvas_x = m_real_canvas_y = 10;
+ return;
+ }
+ int l = (int)((this._CanvasValidBounds.Left + 50) * this._CanvasScale);
+ int t = (int)((this._CanvasValidBounds.Top + 50) * this._CanvasScale);
+ int r = (int)((this._CanvasValidBounds.Right - 50) * this._CanvasScale);
+ int b = (int)((this._CanvasValidBounds.Bottom - 50) * this._CanvasScale);
+ if (r + x < 0) x = -r;
+ if (this.Width - l < x) x = this.Width - l;
+ if (b + y < 0) y = -b;
+ if (this.Height - t < y) y = this.Height - t;
+ if (bAnimation) {
+ if ((ma & CanvasMoveArgs.Left) == CanvasMoveArgs.Left)
+ m_real_canvas_x = x;
+ if ((ma & CanvasMoveArgs.Top) == CanvasMoveArgs.Top)
+ m_real_canvas_y = y;
+ } else {
+ m_real_canvas_x = this._CanvasOffsetX = x;
+ m_real_canvas_y = this._CanvasOffsetY = y;
+ }
+ this.OnCanvasMoved(new EventArgs());
+ }
+ ///
+ /// 缩放画布
+ /// 当不存在 Node 时候 无法缩放
+ ///
+ /// 缩放比例
+ /// 以指定控件坐标 X 为中心进行缩放
+ /// 以指定控件坐标 Y 为中心进行缩放
+ public void ScaleCanvas(float f, float x, float y) {
+ if (this._Nodes.Count == 0) {
+ this._CanvasScale = 1F;
+ return;
+ }
+ if (this._CanvasScale == f) return;
+ if (f < 0.5) f = 0.5f; else if (f > 3) f = 3;
+ float x_c = this.ControlToCanvas(x, true);
+ float y_c = this.ControlToCanvas(y, false);
+ this._CanvasScale = f;
+ this._CanvasOffsetX = m_real_canvas_x -= this.CanvasToControl(x_c, true) - x;
+ this._CanvasOffsetY = m_real_canvas_y -= this.CanvasToControl(y_c, false) - y;
+ this.OnCanvasScaled(new EventArgs());
+ this.Invalidate();
+ }
+ ///
+ /// 获取当前已连接的 Option 对应关系
+ ///
+ /// 连接信息集合
+ public ConnectionInfo[] GetConnectionInfo() {
+ return m_dic_gp_info.Values.ToArray();
+ }
+ ///
+ /// 判断两个 Node 之间是否存在连接路径
+ ///
+ /// 起始 Node
+ /// 目标 Node
+ /// 若存在路径返回true 否则false
+ public static bool CanFindNodePath(STNode nodeStart, STNode nodeFind) {
+ HashSet hs = new HashSet();
+ return STNodeEditor.CanFindNodePath(nodeStart, nodeFind, hs);
+ }
+ private static bool CanFindNodePath(STNode nodeStart, STNode nodeFind, HashSet hs) {
+ foreach (STNodeOption op_1 in nodeStart.OutputOptions) {
+ foreach (STNodeOption op_2 in op_1.ConnectedOption) {
+ if (op_2.Owner == nodeFind) return true;
+ if (hs.Add(op_2.Owner)) {
+ if (STNodeEditor.CanFindNodePath(op_2.Owner, nodeFind)) return true;
+ }
+ }
+ }
+ return false;
+ }
+ ///
+ /// 获取画布中指定矩形区域图像
+ ///
+ /// 画布中指定的矩形区域
+ /// 图像
+ public Image GetCanvasImage(Rectangle rect) { return this.GetCanvasImage(rect, 1f); }
+ ///
+ /// 获取画布中指定矩形区域图像
+ ///
+ /// 画布中指定的矩形区域
+ /// 缩放比例
+ /// 图像
+ public Image GetCanvasImage(Rectangle rect, float fScale) {
+ if (fScale < 0.5) fScale = 0.5f; else if (fScale > 3) fScale = 3;
+ Image img = new Bitmap((int)(rect.Width * fScale), (int)(rect.Height * fScale));
+ using (Graphics g = Graphics.FromImage(img)) {
+ g.Clear(this.BackColor);
+ g.ScaleTransform(fScale, fScale);
+ m_drawing_tools.Graphics = g;
+
+ if (this._ShowGrid) this.OnDrawGrid(m_drawing_tools, rect.Width, rect.Height);
+ g.TranslateTransform(-rect.X, -rect.Y); //移动坐标系
+ this.OnDrawNode(m_drawing_tools, rect);
+ this.OnDrawConnectedLine(m_drawing_tools);
+
+ g.ResetTransform();
+
+ if (this._ShowLocation) this.OnDrawNodeOutLocation(m_drawing_tools, img.Size, m_lst_node_out);
+ }
+ return img;
+ }
+ ///
+ /// 保存画布中的类容到文件中
+ ///
+ /// 文件路径
+ public void SaveCanvas(string strFileName) {
+ using (FileStream fs = new FileStream(strFileName, FileMode.Create, FileAccess.Write)) {
+ this.SaveCanvas(fs);
+ }
+ }
+ ///
+ /// 保存画布中的类容到数据流
+ ///
+ /// 数据流对象
+ public void SaveCanvas(Stream s) {
+ Dictionary dic = new Dictionary();
+ s.Write(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0, 4); //file head
+ s.WriteByte(1); //ver
+ s.Write(BitConverter.GetBytes(this._CanvasOffsetX), 0, 4);
+ s.Write(BitConverter.GetBytes(this._CanvasOffsetY), 0, 4);
+ s.Write(BitConverter.GetBytes(this._CanvasScale), 0, 4);
+ s.Write(BitConverter.GetBytes(this._Nodes.Count), 0, 4);
+ foreach (STNode node in this._Nodes) {
+ try {
+ byte[] byNode = node.GetSaveData();
+ s.Write(BitConverter.GetBytes(byNode.Length), 0, 4);
+ s.Write(byNode, 0, byNode.Length);
+ foreach (STNodeOption op in node.InputOptions) dic.Add(op, dic.Count);
+ foreach (STNodeOption op in node.OutputOptions) dic.Add(op, dic.Count);
+ } catch (Exception ex) {
+ throw new Exception("获取节点数据出错-" + node.Title, ex);
+ }
+ }
+ s.Write(BitConverter.GetBytes(m_dic_gp_info.Count), 0, 4);
+ foreach (var v in m_dic_gp_info.Values)
+ s.Write(BitConverter.GetBytes(((dic[v.Output] << 32) | dic[v.Input])), 0, 8);
+ }
+ ///
+ /// 获取画布中内容二进制数据
+ ///
+ /// 二进制数据
+ public byte[] GetCanvasData() {
+ using (MemoryStream ms = new MemoryStream()) {
+ this.SaveCanvas(ms);
+ return ms.ToArray();
+ }
+ }
+ ///
+ /// 加载程序集
+ ///
+ /// 程序集集合
+ /// 存在STNode类型的文件的个数
+ public int LoadAssembly(string[] strFiles) {
+ int nCount = 0;
+ foreach (var v in strFiles) {
+ try {
+ if (this.LoadAssembly(v)) nCount++;
+ } catch { }
+ }
+ return nCount;
+ }
+ ///
+ /// 加载程序集
+ ///
+ /// 指定需要加载的文件
+ /// 是否加载成功
+ public bool LoadAssembly(string strFile) {
+ bool bFound = false;
+ Assembly asm = Assembly.LoadFrom(strFile);
+ if (asm == null) return false;
+ foreach (var t in asm.GetTypes()) {
+ if (t.IsAbstract) continue;
+ if (t == m_type_node || t.IsSubclassOf(m_type_node)) {
+ if (m_dic_type.ContainsKey(t.GUID.ToString())) continue;
+ m_dic_type.Add(t.GUID.ToString(), t);
+ bFound = true;
+ }
+ }
+ return bFound;
+ }
+ ///
+ /// 获取当前编辑器中已加载的Node类型
+ ///
+ /// 类型集合
+ public Type[] GetTypes() {
+ return m_dic_type.Values.ToArray();
+ }
+ ///
+ /// 从文件中加载数据
+ /// 注意: 此方法并不会清空画布中数据 而是数据叠加
+ ///
+ /// 文件路径
+ public void LoadCanvas(string strFileName) {
+ using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(strFileName)))
+ this.LoadCanvas(ms);
+ }
+ ///
+ /// 从二进制加载数据
+ /// 注意: 此方法并不会清空画布中数据 而是数据叠加
+ ///
+ /// 二进制数据
+ public void LoadCanvas(byte[] byData) {
+ using (MemoryStream ms = new MemoryStream(byData))
+ this.LoadCanvas(ms);
+ }
+ ///
+ /// 从数据流中加载数据
+ /// 注意: 此方法并不会清空画布中数据 而是数据叠加
+ ///
+ /// 数据流对象
+ public void LoadCanvas(Stream s) {
+ int nLen = 0;
+ byte[] byLen = new byte[4];
+ s.Read(byLen, 0, 4);
+ if (BitConverter.ToInt32(byLen, 0) != BitConverter.ToInt32(new byte[] { (byte)'S', (byte)'T', (byte)'N', (byte)'D' }, 0))
+ throw new InvalidDataException("无法识别的文件类型");
+ if (s.ReadByte() != 1) throw new InvalidDataException("无法识别的文件版本号");
+ s.Read(byLen, 0, 4);
+ float x = BitConverter.ToSingle(byLen, 0);
+ s.Read(byLen, 0, 4);
+ float y = BitConverter.ToSingle(byLen, 0);
+ s.Read(byLen, 0, 4);
+ float scale = BitConverter.ToSingle(byLen, 0);
+ s.Read(byLen, 0, 4);
+ int nCount = BitConverter.ToInt32(byLen, 0);
+ Dictionary dic = new Dictionary();
+ byte[] byData = null;
+ for (int i = 0; i < nCount; i++) {
+ s.Read(byLen, 0, byLen.Length);
+ nLen = BitConverter.ToInt32(byLen, 0);
+ byData = new byte[nLen];
+ s.Read(byData, 0, byData.Length);
+ STNode node = this.GetNodeFromData(byData);
+ try { this._Nodes.Add(node); } catch (Exception ex) { throw new Exception("加载节点出错-" + node.Title, ex); }
+ foreach (STNodeOption op in node.InputOptions) dic.Add(dic.Count, op);
+ foreach (STNodeOption op in node.OutputOptions) dic.Add(dic.Count, op);
+ }
+ s.Read(byLen, 0, 4);
+ nCount = BitConverter.ToInt32(byLen, 0);
+ byData = new byte[8];
+ for (int i = 0; i < nCount; i++) {
+ s.Read(byData, 0, byData.Length);
+ long id = BitConverter.ToInt64(byData, 0);
+ long op_out = id >> 32;
+ long op_in = (int)id;
+ dic[op_out].ConnectOption(dic[op_in]);
+ }
+ this.ScaleCanvas(scale, 0, 0);
+ this.MoveCanvas(x, y, false, CanvasMoveArgs.All);
+ this.BuildBounds();
+ }
+ private STNode GetNodeFromData(byte[] byData) {
+ int nIndex = 0;
+ string strGUID = Encoding.UTF8.GetString(byData, nIndex + 1, byData[nIndex]);
+ nIndex += byData[nIndex] + 1;
+
+ int nLen = 0;
+
+ Dictionary dic = new Dictionary();
+ while (nIndex < byData.Length) {
+ nLen = BitConverter.ToInt32(byData, nIndex);
+ nIndex += 4;
+ string strKey = Encoding.UTF8.GetString(byData, nIndex, nLen);
+ nIndex += nLen;
+ nLen = BitConverter.ToInt32(byData, nIndex);
+ nIndex += 4;
+ byte[] byValue = new byte[nLen];
+ Array.Copy(byData, nIndex, byValue, 0, nLen);
+ nIndex += nLen;
+ dic.Add(strKey, byValue);
+ }
+ if (!m_dic_type.ContainsKey(strGUID)) throw new TypeLoadException("无法找到类型 {" + strGUID + "} 所在程序集 确保所需程序集已被编辑器正确加载 可通过调用LoadAssembly()加载程序集");
+ Type t = m_dic_type[strGUID]; ;
+ STNode node = (STNode)Activator.CreateInstance(t);
+ node.OnLoadNode(dic);
+ return node;
+ }
+ ///
+ /// 在画布中显示提示信息
+ ///
+ /// 要显示的信息
+ /// 信息前景色
+ /// 信息背景色
+ public void ShowAlert(string strText, Color foreColor, Color backColor) {
+ this.ShowAlert(strText, foreColor, backColor, 1000, AlertLocation.LeftBottom, true);
+ }
+ ///
+ /// 在画布中显示提示信息
+ ///
+ /// 要显示的信息
+ /// 信息前景色
+ /// 信息背景色
+ /// 信息要显示的位置
+ public void ShowAlert(string strText, Color foreColor, Color backColor, AlertLocation al) {
+ this.ShowAlert(strText, foreColor, backColor, 1000, al, true);
+ }
+ ///
+ /// 在画布中显示提示信息
+ ///
+ /// 要显示的信息
+ /// 信息前景色
+ /// 信息背景色
+ /// 信息持续时间
+ /// 信息要显示的位置
+ /// 是否立即重绘
+ public void ShowAlert(string strText, Color foreColor, Color backColor, int nTime, AlertLocation al, bool bRedraw) {
+ m_str_alert = strText;
+ m_forecolor_alert = foreColor;
+ m_backcolor_alert = backColor;
+ m_time_alert = nTime;
+ m_dt_alert = DateTime.Now;
+ m_alpha_alert = 255;
+ m_al = al;
+ if (bRedraw) this.Invalidate();
+ }
+ ///
+ /// 设置画布中活动的节点
+ ///
+ /// 需要被设置为活动的节点
+ /// 设置前的活动节点
+ public STNode SetActiveNode(STNode node) {
+ STNode ret = this._ActiveNode;
+ if (this._ActiveNode != node) { //重置活动选择节点
+ if (node != null) {
+ node.IsSelected = node.IsActive = true;
+ node.OnGotFocus(new EventArgs());
+ }
+ if (this._ActiveNode != null) {
+ this._ActiveNode.IsActive = this._ActiveNode.IsSelected = false;
+ this._ActiveNode.OnLostFocus(new EventArgs());
+ }
+ this._ActiveNode = node;
+ this.Invalidate();
+ this.OnSelectedChanged(new EventArgs());
+ }
+ return ret;
+ }
+ ///
+ /// 向编辑器中添加默认数据类型颜色
+ ///
+ /// 数据类型
+ /// 对应颜色
+ public void SetTypeColor(Type t, Color clr) {
+ if (this._TypeColor.ContainsKey(t))
+ this._TypeColor[t] = clr;
+ else
+ this._TypeColor.Add(t, clr);
+ }
+
+ #endregion public
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs b/ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs
new file mode 100755
index 0000000..5a8cf03
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeEditorDataType.cs
@@ -0,0 +1,198 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.ComponentModel;
+using System.Drawing;
+
+namespace ST.Library.UI
+{
+ public enum ConnectionStatus
+ {
+ ///
+ /// 不存在所有者
+ ///
+ [Description("不存在所有者")]
+ NoOwner,
+ ///
+ /// 相同的所有者
+ ///
+ [Description("相同的所有者")]
+ SameOwner,
+ ///
+ /// 均为输入或者输出选项
+ ///
+ [Description("均为输入或者输出选项")]
+ SameInputOrOutput,
+ ///
+ /// 不同的数据类型
+ ///
+ [Description("不同的数据类型")]
+ ErrorType,
+ ///
+ /// 单连接节点
+ ///
+ [Description("单连接节点")]
+ SingleOption,
+ ///
+ /// 出现环形路径
+ ///
+ [Description("出现环形路径")]
+ Loop,
+ ///
+ /// 已存在的连接
+ ///
+ [Description("已存在的连接")]
+ Exists,
+ ///
+ /// 已经连接
+ ///
+ [Description("已经连接")]
+ Connected,
+ ///
+ /// 连接被断开
+ ///
+ [Description("连接被断开")]
+ DisConnected,
+ ///
+ /// 节点被锁定
+ ///
+ [Description("节点被锁定")]
+ Locked,
+ ///
+ /// 操作被拒绝
+ ///
+ [Description("操作被拒绝")]
+ Reject,
+ ///
+ /// 正在被连接
+ ///
+ [Description("正在被连接")]
+ Connecting,
+ ///
+ /// 正在断开连接
+ ///
+ [Description("正在断开连接")]
+ DisConnecting
+ }
+
+ public enum AlertLocation
+ {
+ Left,
+ Top,
+ Right,
+ Bottom,
+ Center,
+ LeftTop,
+ RightTop,
+ RightBottom,
+ LeftBottom,
+ }
+
+ public struct DrawingTools
+ {
+ public Graphics Graphics;
+ public Pen Pen;
+ public SolidBrush SolidBrush;
+ }
+
+ public enum CanvasMoveArgs //移动画布时需要的参数 查看->MoveCanvas()
+ {
+ Left = 1, //表示 仅移动 X 坐标
+ Top = 2, //表示 仅移动 Y 坐标
+ All = 4 //表示 X Y 同时移动
+ }
+
+ public struct NodeFindInfo
+ {
+ public STNode Node;
+ public STNodeOption NodeOption;
+ public string Mark;
+ public string[] MarkLines;
+ }
+
+ public struct ConnectionInfo
+ {
+ public STNodeOption Input;
+ public STNodeOption Output;
+ }
+
+ public delegate void STNodeOptionEventHandler(object sender, STNodeOptionEventArgs e);
+
+ public class STNodeOptionEventArgs : EventArgs
+ {
+ private STNodeOption _TargetOption;
+ ///
+ /// 触发此事件的对应Option
+ ///
+ public STNodeOption TargetOption {
+ get { return _TargetOption; }
+ }
+
+ private ConnectionStatus _Status;
+ ///
+ /// Option之间的连线状态
+ ///
+ public ConnectionStatus Status {
+ get { return _Status; }
+ internal set { _Status = value; }
+ }
+
+ private bool _IsSponsor;
+ ///
+ /// 是否为此次行为的发起者
+ ///
+ public bool IsSponsor {
+ get { return _IsSponsor; }
+ }
+
+ public STNodeOptionEventArgs(bool isSponsor, STNodeOption opTarget, ConnectionStatus cr) {
+ this._IsSponsor = isSponsor;
+ this._TargetOption = opTarget;
+ this._Status = cr;
+ }
+ }
+
+ public delegate void STNodeEditorEventHandler(object sender, STNodeEditorEventArgs e);
+ public delegate void STNodeEditorOptionEventHandler(object sender, STNodeEditorOptionEventArgs e);
+
+
+ public class STNodeEditorEventArgs : EventArgs
+ {
+ private STNode _Node;
+
+ public STNode Node {
+ get { return _Node; }
+ }
+
+ public STNodeEditorEventArgs(STNode node) {
+ this._Node = node;
+ }
+ }
+
+ public class STNodeEditorOptionEventArgs : STNodeOptionEventArgs
+ {
+
+ private STNodeOption _CurrentOption;
+ ///
+ /// 主动触发事件的Option
+ ///
+ public STNodeOption CurrentOption {
+ get { return _CurrentOption; }
+ }
+
+ private bool _Continue = true;
+ ///
+ /// 是否继续向下操作 用于Begin(Connecting/DisConnecting)是否继续向后操作
+ ///
+ public bool Continue {
+ get { return _Continue; }
+ set { _Continue = value; }
+ }
+
+ public STNodeEditorOptionEventArgs(STNodeOption opTarget, STNodeOption opCurrent, ConnectionStatus cr)
+ : base(false, opTarget, cr) {
+ this._CurrentOption = opCurrent;
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeHub.cs b/ST.Library.UI/STNodeEditor/STNodeHub.cs
new file mode 100755
index 0000000..70f4cbb
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeHub.cs
@@ -0,0 +1,147 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ST.Library.UI
+{
+ public class STNodeHub : STNode
+ {
+ private bool m_bSingle;
+ private string m_strIn;
+ private string m_strOut;
+
+ public STNodeHub() : this(false, "IN", "OUT") { }
+ public STNodeHub(bool bSingle) : this(bSingle, "IN", "OUT") { }
+ public STNodeHub(bool bSingle, string strTextIn, string strTextOut) {
+ m_bSingle = bSingle;
+ m_strIn = strTextIn;
+ m_strOut = strTextOut;
+ this.Addhub();
+ this.Title = "HUB";
+ this.TitleColor = System.Drawing.Color.FromArgb(200, System.Drawing.Color.DarkOrange);
+ }
+
+ private void Addhub() {
+ var input = new STNodeHubOption(m_strIn, typeof(object), m_bSingle);
+ var output = new STNodeHubOption(m_strOut, typeof(object), false);
+ this.InputOptions.Add(input);
+ this.OutputOptions.Add(output);
+ input.Connected += new STNodeOptionEventHandler(input_Connected);
+ input.DataTransfer += new STNodeOptionEventHandler(input_DataTransfer);
+ input.DisConnected += new STNodeOptionEventHandler(input_DisConnected);
+ output.Connected += new STNodeOptionEventHandler(output_Connected);
+ output.DisConnected += new STNodeOptionEventHandler(output_DisConnected);
+ }
+
+ void output_DisConnected(object sender, STNodeOptionEventArgs e) {
+ STNodeOption op = sender as STNodeOption;
+ if (op.ConnectionCount != 0) return;
+ int nIndex = this.OutputOptions.IndexOf(op);
+ if (this.InputOptions[nIndex].ConnectionCount != 0) return;
+ this.InputOptions.RemoveAt(nIndex);
+ this.OutputOptions.RemoveAt(nIndex);
+ if (this.Owner != null) this.Owner.BuildLinePath();
+ }
+
+ void output_Connected(object sender, STNodeOptionEventArgs e) {
+ STNodeOption op = sender as STNodeOption;
+ int nIndex = this.OutputOptions.IndexOf(op);
+ var t = typeof(object);
+ if (this.InputOptions[nIndex].DataType == t) {
+ op.DataType = e.TargetOption.DataType;
+ this.InputOptions[nIndex].DataType = op.DataType;
+ foreach (STNodeOption v in this.InputOptions) {
+ if (v.DataType == t) return;
+ }
+ this.Addhub();
+ }
+ }
+
+ void input_DisConnected(object sender, STNodeOptionEventArgs e) {
+ STNodeOption op = sender as STNodeOption;
+ if (op.ConnectionCount != 0) return;
+ int nIndex = this.InputOptions.IndexOf(op);
+ if (this.OutputOptions[nIndex].ConnectionCount != 0) return;
+ this.InputOptions.RemoveAt(nIndex);
+ this.OutputOptions.RemoveAt(nIndex);
+ if (this.Owner != null) this.Owner.BuildLinePath();
+ }
+
+ void input_DataTransfer(object sender, STNodeOptionEventArgs e) {
+ STNodeOption op = sender as STNodeOption;
+ int nIndex = this.InputOptions.IndexOf(op);
+ if (e.Status != ConnectionStatus.Connected)
+ this.OutputOptions[nIndex].Data = null;
+ else
+ this.OutputOptions[nIndex].Data = e.TargetOption.Data;
+ this.OutputOptions[nIndex].TransferData();
+ }
+
+ void input_Connected(object sender, STNodeOptionEventArgs e) {
+ STNodeOption op = sender as STNodeOption;
+ int nIndex = this.InputOptions.IndexOf(op);
+ var t = typeof(object);
+ if (op.DataType == t) {
+ op.DataType = e.TargetOption.DataType;
+ this.OutputOptions[nIndex].DataType = op.DataType;
+ foreach (STNodeOption v in this.InputOptions) {
+ if (v.DataType == t) return;
+ }
+ this.Addhub();
+ } else {
+ //this.OutputOptions[nIndex].Data = e.TargetOption.Data;
+ this.OutputOptions[nIndex].TransferData(e.TargetOption.Data);
+ }
+ }
+
+ protected override void OnSaveNode(Dictionary dic) {
+ dic.Add("count", BitConverter.GetBytes(this.InputOptionsCount));
+ dic.Add("single", new byte[] { (byte)(m_bSingle ? 1 : 0) });
+ dic.Add("strin", Encoding.UTF8.GetBytes(m_strIn));
+ dic.Add("strout", Encoding.UTF8.GetBytes(m_strOut));
+ }
+
+ protected internal override void OnLoadNode(Dictionary dic) {
+ base.OnLoadNode(dic);
+ int nCount = BitConverter.ToInt32(dic["count"], 0);
+ while (this.InputOptionsCount < nCount && this.InputOptionsCount != nCount) this.Addhub();
+ m_bSingle = dic["single"][0] == 1;
+ m_strIn = Encoding.UTF8.GetString(dic["strin"]);
+ m_strOut = Encoding.UTF8.GetString(dic["strout"]);
+ }
+
+ public class STNodeHubOption : STNodeOption
+ {
+ public STNodeHubOption(string strText, Type dataType, bool bSingle) : base(strText, dataType, bSingle) { }
+
+ public override ConnectionStatus ConnectOption(STNodeOption op) {
+ var t = typeof(object);
+ if (this.DataType != t) return base.ConnectOption(op);
+ this.DataType = op.DataType;
+ var ret = base.ConnectOption(op);
+ if (ret != ConnectionStatus.Connected) this.DataType = t;
+ return ret;
+ }
+
+ public override ConnectionStatus CanConnect(STNodeOption op) {
+ if (this.DataType != typeof(object)) return base.CanConnect(op);
+ if (this.IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
+ if (op.Owner == this.Owner) return ConnectionStatus.SameOwner;
+ if (op.Owner == null || this.Owner == null) return ConnectionStatus.NoOwner;
+ if (this.IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
+ if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this.Owner)) return ConnectionStatus.Loop;
+ if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
+ if (op.DataType == typeof(object)) return ConnectionStatus.ErrorType;
+
+ if (!this.IsInput) return ConnectionStatus.Connected;
+ foreach (STNodeOption owner_input in this.Owner.InputOptions) {
+ foreach (STNodeOption o in owner_input.ConnectedOption) {
+ if (o == op) return ConnectionStatus.Exists;
+ }
+ }
+ return ConnectionStatus.Connected; ;
+ }
+ }
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeOption.cs b/ST.Library.UI/STNodeEditor/STNodeOption.cs
new file mode 100755
index 0000000..8b96550
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeOption.cs
@@ -0,0 +1,421 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using System.Drawing;
+
+namespace ST.Library.UI
+{
+ public class STNodeOption
+ {
+ #region Properties
+
+ private STNode _Owner;
+ ///
+ /// 获取当前 Option 所属的 Node
+ ///
+ public STNode Owner {
+ get { return _Owner; }
+ internal set {
+ if (value == _Owner) return;
+ if (_Owner != null) this.DisConnectionAll(); //当所有者变更时 断开当前所有连接
+ _Owner = value;
+ }
+ }
+
+ private bool _IsSingle;
+ ///
+ /// 获取当前 Option 是否仅能被连接一次
+ ///
+ public bool IsSingle {
+ get { return _IsSingle; }
+ }
+
+ private bool _IsInput;
+ ///
+ /// 获取当前 Option 是否是输入选项
+ ///
+ public bool IsInput {
+ get { return _IsInput; }
+ internal set { _IsInput = value; }
+ }
+
+ private Color _TextColor = Color.White;
+ ///
+ /// 获取或设置当前 Option 文本颜色
+ ///
+ public Color TextColor {
+ get { return _TextColor; }
+ protected set {
+ if (value == _TextColor) return;
+ _TextColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private Color _DotColor = Color.Transparent;
+ ///
+ /// 获取或设置当前 Option 连接点的颜色
+ ///
+ public Color DotColor {
+ get { return _DotColor; }
+ protected set {
+ if (value == _DotColor) return;
+ _DotColor = value;
+ this.Invalidate();
+ }
+ }
+
+ private string _Text;
+ ///
+ /// 获取或设置当前 Option 显示文本
+ ///
+ public string Text {
+ get { return _Text; }
+ protected set {
+ if (value == _Text) return;
+ _Text = value;
+ this.Invalidate();
+ }
+ }
+
+ private int _DotLeft;
+ ///
+ /// 获取当前 Option 连接点的左边坐标
+ ///
+ public int DotLeft {
+ get { return _DotLeft; }
+ internal set { _DotLeft = value; }
+ }
+ private int _DotTop;
+ ///
+ /// 获取当前 Option 连接点的上边坐标
+ ///
+ public int DotTop {
+ get { return _DotTop; }
+ internal set { _DotTop = value; }
+ }
+
+ private int _DotSize;
+ ///
+ /// 获取当前 Option 连接点的宽度
+ ///
+ public int DotSize {
+ get { return _DotSize; }
+ protected internal set { _DotSize = value; }
+ }
+
+ private object _Data;
+ ///
+ /// 获取或者设置当前 Option 所包含的数据
+ ///
+ public object Data {
+ get { return _Data; }
+ set {
+ if (value != null) {
+ var t = value.GetType();
+ if (t != this._DataType && !t.IsSubclassOf(this._DataType)) {
+ throw new ArgumentException("无效数据类型 数据类型必须为指定的数据类型或其子类");
+ }
+ }
+ _Data = value;
+ }
+ }
+
+ private Type _DataType;
+ ///
+ /// 获取当前 Option 数据类型
+ ///
+ public Type DataType {
+ get { return _DataType; }
+ internal set { _DataType = value; }
+ }
+
+ //private Rectangle _DotRectangle;
+ ///
+ /// 获取当前 Option 连接点的区域
+ ///
+ public Rectangle DotRectangle {
+ get {
+ return new Rectangle(this._DotLeft, this._DotTop, this._DotSize, this._DotSize);
+ }
+ }
+ ///
+ /// 获取当前 Option 被连接的个数
+ ///
+ public int ConnectionCount {
+ get { return m_hs_connected.Count; }
+ }
+ ///
+ /// 获取当前 Option 所连接的 Option 集合
+ ///
+ internal HashSet ConnectedOption {
+ get { return m_hs_connected; }
+ }
+
+ #endregion Properties
+ ///
+ /// 保存已经被连接的点
+ ///
+ protected HashSet m_hs_connected;
+
+ #region Constructor
+
+ ///
+ /// 构造一个 Option
+ ///
+ /// 显示文本
+ /// 数据类型
+ /// 是否为单连接
+ public STNodeOption(string strText, Type dataType, bool bSingle) {
+ if (dataType == null) throw new ArgumentNullException("指定的数据类型不能为空");
+ this._DotSize = 10;
+ m_hs_connected = new HashSet();
+ this._DataType = dataType;
+ this._Text = strText;
+ this._IsSingle = bSingle;
+ }
+
+ #endregion Constructor
+
+ #region Event
+
+ ///
+ /// 当被连接时候发生
+ ///
+ public event STNodeOptionEventHandler Connected;
+ ///
+ /// 当连接开始发生时发生
+ ///
+ public event STNodeOptionEventHandler Connecting;
+ ///
+ /// 当连接断开时候发生
+ ///
+ public event STNodeOptionEventHandler DisConnected;
+ ///
+ /// 当连接开始断开时发生
+ ///
+ public event STNodeOptionEventHandler DisConnecting;
+ ///
+ /// 当有数据传递时候发生
+ ///
+ public event STNodeOptionEventHandler DataTransfer;
+
+ #endregion Event
+
+ #region protected
+ ///
+ /// 重绘整个控件
+ ///
+ protected void Invalidate() {
+ if (this._Owner == null) return;
+ this._Owner.Invalidate();
+ }
+ /*
+ * 开始我认为应当只有输入类型的选项才具有事件 因为输入是被动的 而输出则是主动的
+ * 但是后来发现 比如在STNodeHub中输出节点就用到了事件
+ * 以防万一所以这里代码注释起来了 也并不是很有问题 输出选项不注册事件也是一样的效果
+ */
+ protected internal virtual void OnConnected(STNodeOptionEventArgs e) {
+ if (this.Connected != null/* && this._IsInput*/) this.Connected(this, e);
+ }
+ protected internal virtual void OnConnecting(STNodeOptionEventArgs e) {
+ if (this.Connecting != null) this.Connecting(this, e);
+ }
+ protected internal virtual void OnDisConnected(STNodeOptionEventArgs e) {
+ if (this.DisConnected != null/* && this._IsInput*/) this.DisConnected(this, e);
+ }
+ protected internal virtual void OnDisConnecting(STNodeOptionEventArgs e) {
+ if (this.DisConnecting != null) this.DisConnecting(this, e);
+ }
+ protected internal virtual void OnDataTransfer(STNodeOptionEventArgs e) {
+ if (this.DataTransfer != null/* && this._IsInput*/) this.DataTransfer(this, e);
+ }
+ protected void STNodeEidtorConnected(STNodeEditorOptionEventArgs e) {
+ if (this._Owner == null) return;
+ if (this._Owner.Owner == null) return;
+ this._Owner.Owner.OnOptionConnected(e);
+ }
+ protected void STNodeEidtorDisConnected(STNodeEditorOptionEventArgs e) {
+ if (this._Owner == null) return;
+ if (this._Owner.Owner == null) return;
+ this._Owner.Owner.OnOptionDisConnected(e);
+ }
+ ///
+ /// 当前 Option 开始连接目标 Option
+ ///
+ /// 需要连接的 Option
+ /// 是否允许继续操作
+ protected virtual bool ConnectingOption(STNodeOption op) {
+ if (this._Owner == null) return false;
+ if (this._Owner.Owner == null) return false;
+ STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Connecting);
+ this._Owner.Owner.OnOptionConnecting(e);
+ this.OnConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.Connecting));
+ op.OnConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.Connecting));
+ return e.Continue;
+ }
+ ///
+ /// 当前 Option 开始断开目标 Option
+ ///
+ /// 需要断开的 Option
+ /// 是否允许继续操作
+ protected virtual bool DisConnectingOption(STNodeOption op) {
+ if (this._Owner == null) return false;
+ if (this._Owner.Owner == null) return false;
+ STNodeEditorOptionEventArgs e = new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnecting);
+ this._Owner.Owner.OnOptionDisConnecting(e);
+ this.OnDisConnecting(new STNodeOptionEventArgs(true, op, ConnectionStatus.DisConnecting));
+ op.OnDisConnecting(new STNodeOptionEventArgs(false, this, ConnectionStatus.DisConnecting));
+ return e.Continue;
+ }
+
+ #endregion protected
+
+ #region public
+ ///
+ /// 当前 Option 连接目标 Option
+ ///
+ /// 需要连接的 Option
+ /// 连接结果
+ public virtual ConnectionStatus ConnectOption(STNodeOption op) {
+ if (!this.ConnectingOption(op)) {
+ this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
+ return ConnectionStatus.Reject;
+ }
+
+ var v = this.CanConnect(op);
+ if (v != ConnectionStatus.Connected) {
+ this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
+ return v;
+ }
+ v = op.CanConnect(this);
+ if (v != ConnectionStatus.Connected) {
+ this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
+ return v;
+ }
+ op.AddConnection(this, false);
+ this.AddConnection(op, true);
+ this.ControlBuildLinePath();
+
+ this.STNodeEidtorConnected(new STNodeEditorOptionEventArgs(op, this, v));
+ return v;
+ }
+ ///
+ /// 检测当前 Option 是否可以连接目标 Option
+ ///
+ /// 需要连接的 Option
+ /// 检测结果
+ public virtual ConnectionStatus CanConnect(STNodeOption op) {
+ if (this._IsInput == op.IsInput) return ConnectionStatus.SameInputOrOutput;
+ if (op.Owner == this._Owner) return ConnectionStatus.SameOwner;
+ if (op.Owner == null || this._Owner == null) return ConnectionStatus.NoOwner;
+ if (this._IsSingle && m_hs_connected.Count == 1) return ConnectionStatus.SingleOption;
+ if (op.IsInput && STNodeEditor.CanFindNodePath(op.Owner, this._Owner)) return ConnectionStatus.Loop;
+ if (m_hs_connected.Contains(op)) return ConnectionStatus.Exists;
+ if (this._IsInput && op.DataType != this._DataType && !op.DataType.IsSubclassOf(this._DataType)) return ConnectionStatus.ErrorType;
+ if (this._Owner.LockOption) return ConnectionStatus.Locked;
+ return ConnectionStatus.Connected;
+ }
+ ///
+ /// 当前 Option 断开目标 Option
+ ///
+ /// 需要断开的 Option
+ ///
+ public virtual ConnectionStatus DisConnectOption(STNodeOption op) {
+ if (!this.DisConnectingOption(op)) {
+ this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Reject));
+ return ConnectionStatus.Reject;
+ }
+
+ if (op.Owner == null) return ConnectionStatus.NoOwner;
+ if (this._Owner == null) return ConnectionStatus.NoOwner;
+ if (op.Owner.LockOption && this._Owner.LockOption) {
+ this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.Locked));
+ return ConnectionStatus.Locked;
+ }
+ op.RemoveConnection(this, false);
+ this.RemoveConnection(op, true);
+ this.ControlBuildLinePath();
+
+ this.STNodeEidtorDisConnected(new STNodeEditorOptionEventArgs(op, this, ConnectionStatus.DisConnected));
+ return ConnectionStatus.DisConnected;
+ }
+ ///
+ /// 断开当前 Option 的所有连接
+ ///
+ public void DisConnectionAll() {
+ var arr = m_hs_connected.ToArray();
+ foreach (var v in arr) {
+ this.DisConnectOption(v);
+ }
+ }
+ ///
+ /// 获取当前 Option 所连接的 Option 集合
+ ///
+ /// 如果为null 则表示不存在所有者 否则返回集合
+ public List GetConnectedOption() {
+ if (!this._IsInput)
+ return m_hs_connected.ToList();
+ List lst = new List();
+ if (this._Owner == null) return null;
+ if (this._Owner.Owner == null) return null;
+ foreach (var v in this._Owner.Owner.GetConnectionInfo()) {
+ if (v.Output == this) lst.Add(v.Input);
+ }
+ return lst;
+ }
+ ///
+ /// 向当前 Option 所连接的所有 Option 投递数据
+ ///
+ public void TransferData() {
+ foreach (var v in m_hs_connected) {
+ v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
+ }
+ }
+ ///
+ /// 向当前 Option 所连接的所有 Option 投递数据
+ ///
+ /// 需要投递的数据
+ public void TransferData(object data) {
+ this.Data = data; //不是this._Data
+ foreach (var v in m_hs_connected) {
+ v.OnDataTransfer(new STNodeOptionEventArgs(true, this, ConnectionStatus.Connected));
+ }
+ }
+
+ #endregion public
+
+ #region internal
+
+ private bool AddConnection(STNodeOption op, bool bSponsor) {
+ bool b = m_hs_connected.Add(op);
+ this.OnConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
+ if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
+ return b;
+ }
+
+ private bool RemoveConnection(STNodeOption op, bool bSponsor) {
+ bool b = false;
+ if (m_hs_connected.Contains(op)) {
+ b = m_hs_connected.Remove(op);
+ if (this._IsInput) this.OnDataTransfer(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.DisConnected));
+ this.OnDisConnected(new STNodeOptionEventArgs(bSponsor, op, ConnectionStatus.Connected));
+ }
+ return b;
+ }
+
+ #endregion internal
+
+ #region private
+
+ private void ControlBuildLinePath() {
+ if (this.Owner == null) return;
+ if (this.Owner.Owner == null) return;
+ this.Owner.Owner.BuildLinePath();
+ }
+
+ #endregion
+ }
+}
diff --git a/ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs b/ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs
new file mode 100755
index 0000000..762a98d
--- /dev/null
+++ b/ST.Library.UI/STNodeEditor/STNodeOptionCollection.cs
@@ -0,0 +1,224 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using System.Collections;
+
+namespace ST.Library.UI
+{
+ public class STNodeOptionCollection : IList, ICollection, IEnumerable
+ {
+ /*
+ * 虽然该集合提供了完整的数据接口 如:Add,Remove,...
+ * 但是尽可能的不要使用移除的一些操作 如:Remove,RemoveAt,Clear,this[index] = value,...
+ * 因为在我的定义里面 每个Option的Owner是严格绑定的 一些移除或替换等操作会影响到Owner的变更
+ * 所以原本的所有连线将会断开 并且触发DisConnect事件
+ * 为了确保安全在STNode中 仅继承者才能够访问集合
+ */
+ private int _Count;
+ public int Count { get { return _Count; } }
+ private STNodeOption[] m_options;
+ private STNode m_owner;
+
+ private bool m_isInput; //当前集合是否是存放的是输入点
+
+ internal STNodeOptionCollection(STNode owner, bool isInput) {
+ if (owner == null) throw new ArgumentNullException("所有者不能为空");
+ m_owner = owner;
+ m_isInput = isInput;
+ m_options = new STNodeOption[4];
+ }
+
+ public int Add(STNodeOption option) {
+ if (option == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(1);
+ int nIndex = this.IndexOf(option);
+ if (-1 == nIndex) {
+ nIndex = this._Count;
+ option.Owner = m_owner;
+ option.IsInput = m_isInput;
+ m_options[this._Count++] = option;
+ this.Redraw();
+ }
+ return nIndex;
+ }
+
+ public void AddRange(STNodeOption[] options) {
+ if (options == null) throw new ArgumentNullException("添加对象不能为空");
+ this.EnsureSpace(options.Length);
+ foreach (var op in options) {
+ if (op == null) throw new ArgumentNullException("添加对象不能为空");
+ if (-1 == this.IndexOf(op)) {
+ op.Owner = m_owner;
+ op.IsInput = m_isInput;
+ m_options[this._Count++] = op;
+ }
+ }
+ this.Redraw();
+ }
+
+ public void Clear() {
+ for (int i = 0; i < this._Count; i++) m_options[i].Owner = null;
+ this._Count = 0;
+ m_options = new STNodeOption[4];
+ this.Redraw();
+ }
+
+ public bool Contains(STNodeOption option) {
+ return this.IndexOf(option) != -1;
+ }
+
+ public int IndexOf(STNodeOption option) {
+ return Array.IndexOf(m_options, option);
+ }
+
+ public void Insert(int index, STNodeOption option) {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ if (option == null)
+ throw new ArgumentNullException("插入对象不能为空");
+ this.EnsureSpace(1);
+ for (int i = this._Count; i > index; i--)
+ m_options[i] = m_options[i - 1];
+ option.Owner = m_owner;
+ m_options[index] = option;
+ this._Count++;
+ this.Redraw();
+ }
+
+ public bool IsFixedSize {
+ get { return false; }
+ }
+
+ public bool IsReadOnly {
+ get { return false; }
+ }
+
+ public void Remove(STNodeOption option) {
+ int nIndex = this.IndexOf(option);
+ if (nIndex != -1) this.RemoveAt(nIndex);
+ }
+
+ public void RemoveAt(int index) {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ this._Count--;
+ m_options[index].Owner = null;
+ for (int i = index, Len = this._Count; i < Len; i++)
+ m_options[i] = m_options[i + 1];
+ this.Redraw();
+ }
+
+ public STNodeOption this[int index] {
+ get {
+ if (index < 0 || index >= this._Count)
+ throw new IndexOutOfRangeException("索引越界");
+ return m_options[index];
+ }
+ set { throw new InvalidOperationException("禁止重新赋值元素"); }
+ }
+
+ public void CopyTo(Array array, int index) {
+ if (array == null)
+ throw new ArgumentNullException("数组不能为空");
+ m_options.CopyTo(array, index);
+ }
+
+ public bool IsSynchronized {
+ get { return true; }
+ }
+
+ public object SyncRoot {
+ get { return this; }
+ }
+
+ public IEnumerator GetEnumerator() {
+ for (int i = 0, Len = this._Count; i < Len; i++)
+ yield return m_options[i];
+ }
+ ///
+ /// 确认空间是否足够 空间不足扩大容量
+ ///
+ /// 需要增加的个数
+ private void EnsureSpace(int elements) {
+ if (elements + this._Count > m_options.Length) {
+ STNodeOption[] arrTemp = new STNodeOption[Math.Max(m_options.Length * 2, elements + this._Count)];
+ m_options.CopyTo(arrTemp, 0);
+ m_options = arrTemp;
+ }
+ }
+
+ protected void Redraw() {
+ if (m_owner != null && m_owner.Owner != null) {
+ m_owner.BuildSize(true, true, true);
+ //m_owner.Invalidate();//.Owner.Invalidate();
+ }
+ }
+ //===================================================================================
+ int IList.Add(object value) {
+ return this.Add((STNodeOption)value);
+ }
+
+ void IList.Clear() {
+ this.Clear();
+ }
+
+ bool IList.Contains(object value) {
+ return this.Contains((STNodeOption)value);
+ }
+
+ int IList.IndexOf(object value) {
+ return this.IndexOf((STNodeOption)value);
+ }
+
+ void IList.Insert(int index, object value) {
+ this.Insert(index, (STNodeOption)value);
+ }
+
+ bool IList.IsFixedSize {
+ get { return this.IsFixedSize; }
+ }
+
+ bool IList.IsReadOnly {
+ get { return this.IsReadOnly; }
+ }
+
+ void IList.Remove(object value) {
+ this.Remove((STNodeOption)value);
+ }
+
+ void IList.RemoveAt(int index) {
+ this.RemoveAt(index);
+ }
+
+ object IList.this[int index] {
+ get {
+ return this[index];
+ }
+ set {
+ this[index] = (STNodeOption)value;
+ }
+ }
+
+ void ICollection.CopyTo(Array array, int index) {
+ this.CopyTo(array, index);
+ }
+
+ int ICollection.Count {
+ get { return this._Count; }
+ }
+
+ bool ICollection.IsSynchronized {
+ get { return this.IsSynchronized; }
+ }
+
+ object ICollection.SyncRoot {
+ get { return this.SyncRoot; }
+ }
+
+ IEnumerator IEnumerable.GetEnumerator() {
+ return this.GetEnumerator();
+ }
+ }
+}
\ No newline at end of file
diff --git a/WinNodeEditerTest.sln b/WinNodeEditerTest.sln
new file mode 100755
index 0000000..e538a82
--- /dev/null
+++ b/WinNodeEditerTest.sln
@@ -0,0 +1,42 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinNodeEditerTest", "WinNodeEditerTest\WinNodeEditerTest.csproj", "{8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ST.Library.UI", "ST.Library.UI\ST.Library.UI.csproj", "{EFFCC270-4999-4077-A543-56CCCCE92147}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|x86.ActiveCfg = Debug|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Debug|x86.Build.0 = Debug|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Any CPU.ActiveCfg = Release|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|Mixed Platforms.Build.0 = Release|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|x86.ActiveCfg = Release|x86
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}.Release|x86.Build.0 = Release|x86
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {EFFCC270-4999-4077-A543-56CCCCE92147}.Release|x86.ActiveCfg = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/WinNodeEditerTest.suo b/WinNodeEditerTest.suo
new file mode 100755
index 0000000..a85da7b
Binary files /dev/null and b/WinNodeEditerTest.suo differ
diff --git a/WinNodeEditerTest/DemoNode.cs b/WinNodeEditerTest/DemoNode.cs
new file mode 100755
index 0000000..a5299bf
--- /dev/null
+++ b/WinNodeEditerTest/DemoNode.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using ST.Library.UI;
+
+namespace ST.Library.UI
+{
+ public class DemoNode : STNode
+ {
+ protected override void OnCreate() {
+ base.OnCreate();
+ this.InputOptions.Add(new STNodeOption("Input", typeof(string), false));
+ this.InputOptions.Add(new STNodeOption("SingleNode", typeof(System.Drawing.Image), true));
+ this.InputOptions.Add(new STNodeOption("SingleNode", typeof(object), true));
+
+ this.OutputOptions.Add(new STNodeOption("output", typeof(string), false));
+ this.OutputOptions.Add(new STNodeOption("Single", typeof(System.Drawing.Icon), true));
+ this.OutputOptions.Add(new STNodeOption("Single", typeof(object), true));
+
+ this.Title = "Demo_Node";
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Demo_Image/FrmImage.Designer.cs b/WinNodeEditerTest/Demo_Image/FrmImage.Designer.cs
new file mode 100755
index 0000000..2846ad1
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/FrmImage.Designer.cs
@@ -0,0 +1,87 @@
+namespace ST.Library.UI.Demo_Image
+{
+ partial class FrmImage
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing) {
+ if (disposing && (components != null)) {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent() {
+ this.button1 = new System.Windows.Forms.Button();
+ this.button2 = new System.Windows.Forms.Button();
+ this.stNodeEditor1 = new ST.Library.UI.STNodeEditor();
+ this.SuspendLayout();
+ //
+ // button1
+ //
+ this.button1.Location = new System.Drawing.Point(0, 0);
+ this.button1.Name = "button1";
+ this.button1.Size = new System.Drawing.Size(75, 23);
+ this.button1.TabIndex = 1;
+ this.button1.Text = "button1";
+ this.button1.UseVisualStyleBackColor = true;
+ this.button1.Click += new System.EventHandler(this.button1_Click);
+ //
+ // button2
+ //
+ this.button2.Location = new System.Drawing.Point(0, 29);
+ this.button2.Name = "button2";
+ this.button2.Size = new System.Drawing.Size(75, 23);
+ this.button2.TabIndex = 2;
+ this.button2.Text = "button2";
+ this.button2.UseVisualStyleBackColor = true;
+ this.button2.Click += new System.EventHandler(this.button2_Click);
+ //
+ // stNodeEditor1
+ //
+ this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
+ this.stNodeEditor1.Curvature = 0.3F;
+ this.stNodeEditor1.Location = new System.Drawing.Point(0, 0);
+ this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.Name = "stNodeEditor1";
+ this.stNodeEditor1.Size = new System.Drawing.Size(200, 200);
+ this.stNodeEditor1.TabIndex = 0;
+ this.stNodeEditor1.Text = "stNodeEditor1";
+ //
+ // FrmImage
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(292, 273);
+ this.Controls.Add(this.button2);
+ this.Controls.Add(this.button1);
+ this.Controls.Add(this.stNodeEditor1);
+ this.Name = "FrmImage";
+ this.Text = "FrmImage";
+ this.Load += new System.EventHandler(this.FrmImage_Load);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private STNodeEditor stNodeEditor1;
+ private System.Windows.Forms.Button button1;
+ private System.Windows.Forms.Button button2;
+ }
+}
\ No newline at end of file
diff --git a/WinNodeEditerTest/Demo_Image/FrmImage.cs b/WinNodeEditerTest/Demo_Image/FrmImage.cs
new file mode 100755
index 0000000..b76d849
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/FrmImage.cs
@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+using System.IO;
+
+namespace ST.Library.UI.Demo_Image
+{
+ public partial class FrmImage : Form
+ {
+ public FrmImage() {
+ InitializeComponent();
+ button1.Text = "Save";
+ button2.Text = "Open";
+ }
+
+ private void FrmImage_Load(object sender, EventArgs e) {
+ stNodeEditor1.Dock = DockStyle.Fill;
+ stNodeEditor1.TypeColor.Add(typeof(Image), Color.BlueViolet);
+
+ STNode node = new STNodeImageInput();
+ stNodeEditor1.Nodes.Add(node);
+
+ node = new STNodeImageChannel();
+ stNodeEditor1.Nodes.Add(node);
+
+
+ node = new STNodeImageChannel();
+ stNodeEditor1.Nodes.Add(node);
+ node = new STNodeImageChannel();
+ stNodeEditor1.Nodes.Add(node);
+ node = new STNodeImageChannel();
+ stNodeEditor1.Nodes.Add(node);
+
+ stNodeEditor1.LoadAssembly(Application.ExecutablePath);
+ stNodeEditor1.LoadAssembly(Directory.GetFiles("./", "*.dll"));
+ }
+
+ private void button1_Click(object sender, EventArgs e) {
+ SaveFileDialog sfd = new SaveFileDialog();
+ sfd.Filter = "*.stn|*.stn";
+ if (sfd.ShowDialog() != DialogResult.OK) return;
+ stNodeEditor1.SaveCanvas(sfd.FileName);
+ }
+
+ private void button2_Click(object sender, EventArgs e) {
+ OpenFileDialog ofd = new OpenFileDialog();
+ ofd.Filter = "*.stn|*.stn";
+ if (ofd.ShowDialog() != DialogResult.OK) return;
+ stNodeEditor1.Nodes.Clear();
+ stNodeEditor1.LoadCanvas(ofd.FileName);
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Demo_Image/FrmImage.resx b/WinNodeEditerTest/Demo_Image/FrmImage.resx
new file mode 100755
index 0000000..29dcb1b
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/FrmImage.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/WinNodeEditerTest/Demo_Image/STNodeImage.cs b/WinNodeEditerTest/Demo_Image/STNodeImage.cs
new file mode 100755
index 0000000..43e7515
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/STNodeImage.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+namespace ST.Library.UI.Demo_Image
+{
+ public class STNodeImage : STNode
+ {
+ protected STNodeOption m_input_image; //输入输出点
+ protected STNodeOption m_out_image;
+
+ protected override void OnCreate() {
+ base.OnCreate();
+ m_input_image = new STNodeOption("", typeof(Image), true);
+ this.InputOptions.Add(m_input_image);
+ m_out_image = new STNodeOption("", typeof(Image), false);
+ this.OutputOptions.Add(m_out_image);
+ m_input_image.DataTransfer += new STNodeOptionEventHandler(m_input_image_DataTransfer);
+ this.Title = "Image";
+ }
+ //监听输入点接入事件
+ void m_input_image_DataTransfer(object sender, STNodeOptionEventArgs e) {
+ if (e.Status != ConnectionStatus.Connected)
+ m_input_image.Data = null;
+ else
+ m_input_image.Data = e.TargetOption.Data;
+ m_out_image.TransferData(m_input_image.Data); //输出节点向下投递数据
+ this.OnDataTransfer(); //通知子类
+ this.Invalidate(); //重绘自己
+ }
+
+ protected override System.Drawing.Size OnBuildNodeSize(DrawingTools dt) {
+ //return base.OnBuildNodeSize();
+ return new System.Drawing.Size(160, 120); //设定节点大小
+ }
+
+ protected override void OnDrawBody(DrawingTools dt) { //重绘节点主体部分
+ base.OnDrawBody(dt);
+ Graphics g = dt.Graphics;
+ g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
+ if (m_input_image.Data != null) {
+ g.DrawImage((Image)m_input_image.Data, this.Left + 15, this.Top + 30, this.Width - 40, this.Height - 40);
+ } else {
+ g.FillRectangle(Brushes.Gray, this.Left + 15, this.Top + 30, this.Width - 40, this.Height - 40);
+ }
+ }
+
+ protected virtual void OnDataTransfer() { }
+ }
+}
diff --git a/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs b/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs
new file mode 100755
index 0000000..3708fd9
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/STNodeImageChannel.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+namespace ST.Library.UI.Demo_Image
+{
+ public class STNodeImageChannel : STNodeImage
+ {
+ private STNodeOption m_out_r;
+ private STNodeOption m_out_g;
+ private STNodeOption m_out_b;
+
+ private Bitmap m_img_r;
+ private Bitmap m_img_g;
+ private Bitmap m_img_b;
+
+ protected override void OnCreate() {
+ base.OnCreate();
+ m_out_r = new STNodeOption("R", typeof(Image), false);
+ m_out_g = new STNodeOption("G", typeof(Image), false);
+ m_out_b = new STNodeOption("B", typeof(Image), false);
+ this.OutputOptions.Add(m_out_r);
+ this.OutputOptions.Add(m_out_g);
+ this.OutputOptions.Add(m_out_b);
+ this.Title = "Channel";
+ }
+
+ protected override void OnDataTransfer() {
+ base.OnDataTransfer();
+ if (m_img_r != null) {
+ m_img_r.Dispose();
+ m_img_g.Dispose();
+ m_img_b.Dispose();
+ m_img_r = m_img_g = m_img_b = null;
+ }
+ if (m_out_image.Data != null) { //分离通道 Demo 演示 Get/SetPixel() 效率极低 应当LockBitmap操作
+ Bitmap img = (Bitmap)base.m_input_image.Data;
+ m_img_r = new Bitmap(img.Width, img.Height);
+ m_img_g = new Bitmap(img.Width, img.Height);
+ m_img_b = new Bitmap(img.Width, img.Height);
+ for (int x = 0; x < img.Width; x++) {
+ for (int y = 0; y < img.Height; y++) {
+ Color clr = img.GetPixel(x, y);
+ m_img_r.SetPixel(x, y, Color.FromArgb(255, clr.R, clr.R, clr.R));
+ m_img_g.SetPixel(x, y, Color.FromArgb(255, clr.G, clr.G, clr.G));
+ m_img_b.SetPixel(x, y, Color.FromArgb(255, clr.B, clr.B, clr.B));
+ }
+ }
+ }
+ m_out_r.TransferData(m_img_r);
+ m_out_g.TransferData(m_img_b);
+ m_out_b.TransferData(m_img_b);
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs b/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs
new file mode 100755
index 0000000..ec54ade
--- /dev/null
+++ b/WinNodeEditerTest/Demo_Image/STNodeImageInput.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+using System.Windows.Forms;
+
+namespace ST.Library.UI.Demo_Image
+{
+ public class STNodeImageInput : STNode
+ {
+ private STNodeOption m_option_out;
+
+ private string m_str_file;
+ private Size m_sz = new Size(100, 60);
+
+ protected override void OnCreate() {
+ base.OnCreate();
+ this.Title = "ImageInput";
+ m_option_out = new STNodeOption("", typeof(Image), false);
+ this.OutputOptions.Add(m_option_out);
+ STNodeButton btn = new STNodeButton();
+ btn.Left = (m_sz.Width - btn.Width) / 2;
+ btn.Top = (m_sz.Height - 20 - btn.Height) / 2;
+ btn.Text = "OpenImage";
+ btn.MouseClick += new MouseEventHandler(btn_MouseClick);
+ this.Controls.Add(btn);
+ }
+
+ void btn_MouseClick(object sender, MouseEventArgs e) {
+ OpenFileDialog ofd = new OpenFileDialog();
+ ofd.Filter = "*.jpg|*.jpg|*.png|*.png|*.bmp|*.bmp|*.*|*.*";
+ if (ofd.ShowDialog() != DialogResult.OK) return;
+ m_option_out.TransferData(Image.FromFile(ofd.FileName));
+ m_str_file = ofd.FileName;
+ }
+
+ protected override Size OnBuildNodeSize(DrawingTools dt) {
+ //return base.OnBuildNodeSize();
+ return m_sz;
+ }
+
+ protected override Point OnSetOptionLocation(STNodeOption op) {
+ return new Point(op.DotLeft, this.Top + 35);
+ //return base.OnSetOptionLocation(op);
+ }
+
+ //protected override void OnDrawOptionDot(DrawingTools dt, STNodeOption op) {
+ // //if (op == m_option_out) op.DotTop = this.Top + 35;
+ // base.OnDrawOptionDot(dt, op);
+ //}
+
+ protected override void OnSaveNode(Dictionary dic) {
+ if (m_str_file == null) m_str_file = string.Empty;
+ dic.Add("file", Encoding.UTF8.GetBytes(m_str_file));
+ }
+
+ protected override void OnLoadNode(Dictionary dic) {
+ base.OnLoadNode(dic);
+ m_str_file = Encoding.UTF8.GetString(dic["file"]);
+ if (System.IO.File.Exists(m_str_file)) { //如果文件存在加载并投递数据
+ m_option_out.TransferData(Image.FromFile(m_str_file));
+ }
+ }
+
+ public class STNodeButton : STNodeControl //自定义一个Button控件
+ {
+ private bool m_isHover;
+
+ protected override void OnMouseEnter(EventArgs e) {
+ base.OnMouseEnter(e);
+ m_isHover = true;
+ this.Invalidate();
+ }
+
+ protected override void OnMouseLeave(EventArgs e) {
+ base.OnMouseLeave(e);
+ m_isHover = false;
+ this.Invalidate();
+ }
+
+ protected override void OnPaint(DrawingTools dt) {
+ //base.OnPaint(dt);
+ Graphics g = dt.Graphics;
+ SolidBrush brush = dt.SolidBrush;
+ brush.Color = m_isHover ? Color.DodgerBlue : this.BackColor;
+ g.FillRectangle(brush, 0, 0, this.Width, this.Height);
+ g.DrawString(this.Text, this.Font, Brushes.White, this.ClientRectangle, base.m_sf);
+ }
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Form1.Designer.cs b/WinNodeEditerTest/Form1.Designer.cs
new file mode 100755
index 0000000..f1ed28a
--- /dev/null
+++ b/WinNodeEditerTest/Form1.Designer.cs
@@ -0,0 +1,100 @@
+namespace ST.Library.UI
+{
+ partial class Form1
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing) {
+ if (disposing && (components != null)) {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent() {
+ this.button1 = new System.Windows.Forms.Button();
+ this.button2 = new System.Windows.Forms.Button();
+ this.button3 = new System.Windows.Forms.Button();
+ this.stNodeEditor1 = new ST.Library.UI.STNodeEditor();
+ this.SuspendLayout();
+ //
+ // button1
+ //
+ this.button1.Location = new System.Drawing.Point(0, 0);
+ this.button1.Name = "button1";
+ this.button1.Size = new System.Drawing.Size(75, 23);
+ this.button1.TabIndex = 4;
+ this.button1.Text = "button1";
+ this.button1.UseVisualStyleBackColor = true;
+ this.button1.Click += new System.EventHandler(this.button1_Click);
+ //
+ // button2
+ //
+ this.button2.Location = new System.Drawing.Point(0, 29);
+ this.button2.Name = "button2";
+ this.button2.Size = new System.Drawing.Size(75, 23);
+ this.button2.TabIndex = 5;
+ this.button2.Text = "button2";
+ this.button2.UseVisualStyleBackColor = true;
+ this.button2.Click += new System.EventHandler(this.button2_Click);
+ //
+ // button3
+ //
+ this.button3.Location = new System.Drawing.Point(0, 58);
+ this.button3.Name = "button3";
+ this.button3.Size = new System.Drawing.Size(75, 23);
+ this.button3.TabIndex = 6;
+ this.button3.Text = "button3";
+ this.button3.UseVisualStyleBackColor = true;
+ this.button3.Click += new System.EventHandler(this.button3_Click);
+ //
+ // stNodeEditor1
+ //
+ this.stNodeEditor1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(34)))), ((int)(((byte)(34)))), ((int)(((byte)(34)))));
+ this.stNodeEditor1.Curvature = 0.3F;
+ this.stNodeEditor1.Location = new System.Drawing.Point(0, 0);
+ this.stNodeEditor1.LocationBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(120)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.MarkBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.MarkForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(180)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
+ this.stNodeEditor1.Name = "stNodeEditor1";
+ this.stNodeEditor1.Size = new System.Drawing.Size(200, 200);
+ this.stNodeEditor1.TabIndex = 3;
+ this.stNodeEditor1.Text = "stNodeEditor1";
+ //
+ // Form1
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(292, 273);
+ this.Controls.Add(this.button3);
+ this.Controls.Add(this.button2);
+ this.Controls.Add(this.button1);
+ this.Controls.Add(this.stNodeEditor1);
+ this.Name = "Form1";
+ this.Text = "Form1";
+ this.Load += new System.EventHandler(this.Form1_Load);
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private STNodeEditor stNodeEditor1;
+ private System.Windows.Forms.Button button1;
+ private System.Windows.Forms.Button button2;
+ private System.Windows.Forms.Button button3;
+ }
+}
\ No newline at end of file
diff --git a/WinNodeEditerTest/Form1.cs b/WinNodeEditerTest/Form1.cs
new file mode 100755
index 0000000..7102db6
--- /dev/null
+++ b/WinNodeEditerTest/Form1.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Windows.Forms;
+
+using ST.Library.UI;
+using System.IO;
+
+namespace ST.Library.UI
+{
+ public partial class Form1 : Form
+ {
+ public Form1() {
+ InitializeComponent();
+ button1.Text = "Lock";
+ button2.Text = "Save";
+ button3.Text = "Open";
+ }
+
+ private void Form1_Load(object sender, EventArgs e) {
+ stNodeEditor1.Dock = DockStyle.Fill;
+ stNodeEditor1.TypeColor.Add(typeof(string), Color.Yellow);
+ stNodeEditor1.TypeColor.Add(typeof(Image), Color.Red);
+
+ stNodeEditor1.SelectedChanged += new EventHandler(stNodeEditor1_SelectedChanged);
+ stNodeEditor1.OptionConnected += new STNodeEditorOptionEventHandler(stNodeEditor1_OptionConnected);
+ stNodeEditor1.CanvasScaled += new EventHandler(stNodeEditor1_CanvasScaled);
+ for (int i = 0; i < 4; i++) {
+ STNode node = new DemoNode();
+ //if (i == 2)
+ // node.Mark = "这里是标记信息\r\n\t新的一行数据";
+ //else
+ // node.Mark = "this is mark Info\r\nthis is new line " + i;
+ stNodeEditor1.Nodes.Add(node);
+ }
+ stNodeEditor1.Nodes.Add(new STNodeHub());
+ stNodeEditor1.Nodes.Add(new STNodeHub());
+
+ stNodeEditor1.Nodes.Add(new NodeNumberAdd());
+
+ stNodeEditor1.LoadAssembly(Application.ExecutablePath);
+ stNodeEditor1.LoadAssembly(Directory.GetFiles("./", "*.dll"));
+ }
+
+ void stNodeEditor1_CanvasScaled(object sender, EventArgs e) {
+ stNodeEditor1.ShowAlert(stNodeEditor1.CanvasScale.ToString("F2"), Color.White, Color.Black);
+ }
+
+ void stNodeEditor1_OptionConnected(object sender, STNodeEditorOptionEventArgs e) {
+ Console.WriteLine(e.CurrentOption.Text + " - " + e.TargetOption.Text + " - " + e.Status);
+ }
+
+ void stNodeEditor1_SelectedChanged(object sender, EventArgs e) {
+ foreach (var v in stNodeEditor1.GetSelectedNode()) {
+ Console.WriteLine("Selected - " + v.Title);
+ }
+ }
+
+ private void button1_Click(object sender, EventArgs e) {
+ stNodeEditor1.Nodes[0].LockOption = !stNodeEditor1.Nodes[0].LockOption;
+ stNodeEditor1.Nodes[1].LockOption = !stNodeEditor1.Nodes[1].LockOption;
+
+
+ stNodeEditor1.Nodes[0].LockLocation = !stNodeEditor1.Nodes[0].LockLocation;
+ stNodeEditor1.Nodes[1].LockLocation = !stNodeEditor1.Nodes[1].LockLocation;
+ }
+
+ private void button2_Click(object sender, EventArgs e) {
+ SaveFileDialog sfd = new SaveFileDialog();
+ sfd.Filter = "*.stn|*.stn";
+ if (sfd.ShowDialog() != DialogResult.OK) return;
+ stNodeEditor1.SaveCanvas(sfd.FileName);
+ }
+
+ private void button3_Click(object sender, EventArgs e) {
+ OpenFileDialog ofd = new OpenFileDialog();
+ ofd.Filter = "*.stn|*.stn";
+ if (ofd.ShowDialog() != DialogResult.OK) return;
+ stNodeEditor1.LoadCanvas(ofd.FileName);
+ stNodeEditor1.Nodes.Clear();
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Form1.resx b/WinNodeEditerTest/Form1.resx
new file mode 100755
index 0000000..29dcb1b
--- /dev/null
+++ b/WinNodeEditerTest/Form1.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/WinNodeEditerTest/NodeNumberAdd.cs b/WinNodeEditerTest/NodeNumberAdd.cs
new file mode 100755
index 0000000..d2c12e4
--- /dev/null
+++ b/WinNodeEditerTest/NodeNumberAdd.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Drawing;
+
+namespace ST.Library.UI
+{
+ public class NodeNumberAdd : STNode
+ {
+ private STNodeOption m_in_num1;
+ private STNodeOption m_in_num2;
+ private STNodeOption m_out_num;
+ private int m_nNum1, m_nNum2;
+
+ protected override void OnCreate() {
+ base.OnCreate();
+ this.Title = "NumberAdd";
+ m_in_num1 = new STNodeOption("num1", typeof(int), true);//只能有一个连线
+ m_in_num2 = new STNodeOption("num2", typeof(int), true);//只能有一个连线
+ m_out_num = new STNodeOption("result", typeof(int), false);//可以多个连线
+ this.InputOptions.Add(m_in_num1);
+ this.InputOptions.Add(m_in_num2);
+ this.OutputOptions.Add(m_out_num);
+ m_in_num1.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer);
+ m_in_num2.DataTransfer += new STNodeOptionEventHandler(m_in_num_DataTransfer);
+ }
+ //当有数据传入时
+ void m_in_num_DataTransfer(object sender, STNodeOptionEventArgs e) {
+ //判断连线是否是连接状态(建立连线 断开连线 都会触发该事件)
+ if (e.Status == ConnectionStatus.Connected) {
+ if (sender == m_in_num1)
+ m_nNum1 = (int)e.TargetOption.Data;//TargetOption为触发此事件的Option
+ else
+ m_nNum2 = (int)e.TargetOption.Data;
+ } else {
+ if (sender == m_in_num1) m_nNum1 = 0; else m_nNum2 = 0;
+ }
+ //向输出选项上的所有连线传输数据 输出选项上的所有连线都会触发 DataTransfer 事件
+ m_out_num.TransferData(m_nNum1 + m_nNum2); //m_out_num.Data 将被自动设置
+ }
+
+ protected override void OnOwnerChanged() {
+ base.OnOwnerChanged();//通常刚被添加到节点编辑器时触发 如是以插件方式提供的节点 应当向编辑器提交数据类型颜色
+ if (this.Owner == null) return; //或者通过m_in_num1.DotColor = Color.Red;进行设置
+ this.Owner.SetTypeColor(typeof(int), Color.Red);
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Program.cs b/WinNodeEditerTest/Program.cs
new file mode 100755
index 0000000..cee5a20
--- /dev/null
+++ b/WinNodeEditerTest/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Windows.Forms;
+
+namespace ST.Library.UI
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main() {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ new Demo_Image.FrmImage().Show();
+ Application.Run(new Form1());
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Properties/AssemblyInfo.cs b/WinNodeEditerTest/Properties/AssemblyInfo.cs
new file mode 100755
index 0000000..49f331d
--- /dev/null
+++ b/WinNodeEditerTest/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("WinNodeEditerTest")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Microsoft")]
+[assembly: AssemblyProduct("WinNodeEditerTest")]
+[assembly: AssemblyCopyright("Copyright © Microsoft 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("2c0c84a0-fb66-4133-80f5-053356fa0f04")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/WinNodeEditerTest/Properties/Resources.Designer.cs b/WinNodeEditerTest/Properties/Resources.Designer.cs
new file mode 100755
index 0000000..bad3293
--- /dev/null
+++ b/WinNodeEditerTest/Properties/Resources.Designer.cs
@@ -0,0 +1,63 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ST.Library.UI.Properties {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ST.Library.UI.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Properties/Resources.resx b/WinNodeEditerTest/Properties/Resources.resx
new file mode 100755
index 0000000..ffecec8
--- /dev/null
+++ b/WinNodeEditerTest/Properties/Resources.resx
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/WinNodeEditerTest/Properties/Settings.Designer.cs b/WinNodeEditerTest/Properties/Settings.Designer.cs
new file mode 100755
index 0000000..9187fcb
--- /dev/null
+++ b/WinNodeEditerTest/Properties/Settings.Designer.cs
@@ -0,0 +1,26 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ST.Library.UI.Properties {
+
+
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
+ internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+
+ private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+
+ public static Settings Default {
+ get {
+ return defaultInstance;
+ }
+ }
+ }
+}
diff --git a/WinNodeEditerTest/Properties/Settings.settings b/WinNodeEditerTest/Properties/Settings.settings
new file mode 100755
index 0000000..abf36c5
--- /dev/null
+++ b/WinNodeEditerTest/Properties/Settings.settings
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/WinNodeEditerTest/WinNodeEditerTest.csproj b/WinNodeEditerTest/WinNodeEditerTest.csproj
new file mode 100755
index 0000000..67964b8
--- /dev/null
+++ b/WinNodeEditerTest/WinNodeEditerTest.csproj
@@ -0,0 +1,111 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {8B0A4516-45C9-4DE9-B947-64E8DA8A72A6}
+ Exe
+ Properties
+ ST.Library.UI
+ WinNodeEditerTest
+ v4.0
+ 512
+
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ FrmImage.cs
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+
+ FrmImage.cs
+
+
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+ True
+
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+ {EFFCC270-4999-4077-A543-56CCCCE92147}
+ ST.Library.UI
+
+
+
+
+
\ No newline at end of file
diff --git a/WinNodeEditerTest/app.config b/WinNodeEditerTest/app.config
new file mode 100755
index 0000000..cb2586b
--- /dev/null
+++ b/WinNodeEditerTest/app.config
@@ -0,0 +1,3 @@
+
+
+