PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/Merlin/Main/Languages/Ruby/Samples/Tutorial/wpf.rb

https://github.com/Catweazle/ironruby
Ruby | 403 lines | 292 code | 65 blank | 46 comment | 18 complexity | 29292a39371306e77d68bc1005f469d4 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-2.1
  1. # ****************************************************************************
  2. #
  3. # Copyright (c) Microsoft Corporation.
  4. #
  5. # This source code is subject to terms and conditions of the Microsoft Public License. A
  6. # copy of the license can be found in the License.html file at the root of this distribution. If
  7. # you cannot locate the Microsoft Public License, please send an email to
  8. # ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
  9. # by the terms of the Microsoft Public License.
  10. #
  11. # You must not remove this notice, or any other, from this software.
  12. #
  13. #
  14. # ****************************************************************************
  15. # Reference the WPF assemblies
  16. require 'system.xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
  17. require 'PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
  18. require 'PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
  19. require 'windowsbase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
  20. class System::Windows::FrameworkElement
  21. # Monkey-patch FrameworkElement to allow window.ChildName instead of window.FindName("ChildName")
  22. # TODO - Make window.child_name work as well
  23. def method_missing name, *args
  24. find_name(name.to_s.to_clr_string) || super
  25. end
  26. def hide!
  27. self.visibility = System::Windows::Visibility.hidden
  28. end
  29. def collapse!
  30. self.visibility = System::Windows::Visibility.collapsed
  31. end
  32. def show!
  33. self.visibility = System::Windows::Visibility.visible
  34. end
  35. def set_or_collapse(property, value)
  36. obj = send(property)
  37. if obj && value
  38. yield obj, value
  39. obj.show!
  40. else
  41. obj.collapse!
  42. end
  43. end
  44. end
  45. class System::Windows::Markup::XamlReader
  46. class << self
  47. alias raw_load load unless method_defined? :raw_load
  48. end
  49. def self.load(xaml)
  50. return raw_load(xaml) unless xaml.respond_to? :to_clr_string
  51. obj = self.Load(
  52. System::Xml::XmlReader.create(
  53. System::IO::StringReader.new(xaml.to_clr_string)))
  54. yield obj if block_given?
  55. obj
  56. end
  57. def self.erb_load(xaml, b, &block)
  58. require 'erb'
  59. self.load(ERB.new(xaml).result(b).to_s, &block)
  60. end
  61. end
  62. class Module
  63. def delegate_methods(methods, opts = {})
  64. raise "methods should be an array" unless methods.kind_of?(Array)
  65. this = self
  66. opts[:to] ||= self
  67. opts[:prepend] = opts[:prepend] ? "#{opts[:prepend]}_" : ''
  68. opts[:append] = if opts[:append]
  69. append = opts[:append]
  70. lambda{|this| "_#{this::send(append)}" }
  71. else
  72. lambda{|this| '' }
  73. end
  74. methods.each do |method|
  75. define_method(method.to_s.to_sym) do
  76. send(opts[:to]).send "#{opts[:prepend]}#{method}#{opts[:append][self]}"
  77. end
  78. end
  79. end
  80. end
  81. class System::Windows::Threading::DispatcherObject
  82. def invoke &block
  83. require "system.core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  84. dispatch_callback = System::Action[].new block
  85. self.dispatcher.invoke(System::Windows::Threading::DispatcherPriority.Normal, dispatch_callback)
  86. end
  87. end
  88. class System::Windows::Documents::FlowDocument
  89. def <<(text)
  90. paragraph = System::Windows::Documents::Paragraph.new
  91. paragraph.inlines.add(System::Windows::Documents::Run.new(text))
  92. self.blocks.add paragraph
  93. end
  94. # Converts text in RDoc simple markup format to a WPF FlowDocument object
  95. def self.from_simple_markup text
  96. require 'rdoc/markup/simple_markup'
  97. require 'rdoc/markup/simple_markup/inline'
  98. # TODO - This is a workaround for http://ironruby.codeplex.com/WorkItem/View.aspx?WorkItemId=1301
  99. text = "#{$1}dummy\n\n#{text}" if text =~ /\A(\s+)/
  100. if not @markupParser
  101. @markupParser = SM::SimpleMarkup.new
  102. # external hyperlinks
  103. @markupParser.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
  104. # and links of the form <text>[<url>]
  105. @markupParser.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
  106. # @markupParser.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)
  107. end
  108. begin
  109. @markupParser.convert(text, Wpf::ToFlowDocument.new)
  110. rescue Exception => e
  111. puts "Error while converting:\n#{text}"
  112. raise e
  113. end
  114. end
  115. end
  116. module Wpf
  117. include System::Windows
  118. include System::Windows::Documents
  119. include System::Windows::Controls
  120. include System::Windows::Input
  121. include System::Windows::Markup
  122. include System::Windows::Media
  123. def self.load_xaml_file(filename)
  124. f = System::IO::FileStream.new filename, System::IO::FileMode.open, System::IO::FileAccess.read
  125. begin
  126. element = XamlReader.load f
  127. ensure
  128. f.close
  129. end
  130. element
  131. end
  132. # Returns an array with all the children, or invokes the block (if given) for each child
  133. # Note that it also includes content (which could be just strings).
  134. def self.walk(tree, &b)
  135. if not block_given?
  136. result = []
  137. walk(tree) { |child| result << child }
  138. return result
  139. end
  140. yield tree
  141. if tree.respond_to? :Children
  142. tree.Children.each { |child| walk child, &b }
  143. elsif tree.respond_to? :Child
  144. walk tree.Child, &b
  145. elsif tree.respond_to? :Content
  146. walk tree.Content, &b
  147. end
  148. end
  149. # If you constructed your treeview with XAML, you should
  150. # use this XAML snippet instead to auto-expand items:
  151. #
  152. # <TreeView.ItemContainerStyle>
  153. # <Style>
  154. # <Setter Property="TreeViewItem.IsExpanded" Value="True"/>
  155. # <Style.Triggers>
  156. # <DataTrigger Binding="{Binding Type}" Value="menu">
  157. # <Setter Property="TreeViewItem.IsSelected" Value="True"/>
  158. # </DataTrigger>
  159. # </Style.Triggers>
  160. # </Style>
  161. # </TreeView.ItemContainerStyle>
  162. #
  163. # If your treeview was constructed with code, use this method
  164. def self.select_tree_view_item(tree_view, item)
  165. return false unless self and item
  166. childNode = tree_view.ItemContainerGenerator.ContainerFromItem item
  167. if childNode
  168. childNode.focus
  169. childNode.IsSelected = true
  170. # TODO - BringIntoView ?
  171. return true
  172. end
  173. if tree_view.Items.Count > 0
  174. tree_view.Items.each do |childItem|
  175. childControl = tree_view.ItemContainerGenerator.ContainerFromItem(childItem)
  176. return false if not childControl
  177. # If tree node is not loaded, its sub-nodes will be nil. Force them to be loaded
  178. old_is_expanded = childControl.is_expanded
  179. childControl.is_expanded = true
  180. childControl.update_layout
  181. if select_tree_view_item childControl, item
  182. return true
  183. else
  184. childControl.is_expanded = old_is_expanded
  185. end
  186. end
  187. end
  188. false
  189. end
  190. def self.create_sta_thread &block
  191. ts = System::Threading::ThreadStart.new &block
  192. # Workaround for http://ironruby.codeplex.com/WorkItem/View.aspx?WorkItemId=1306
  193. param_types = System::Array[System::Type].new(1) { |i| System::Threading::ThreadStart.to_clr_type }
  194. ctor = System::Threading::Thread.to_clr_type.get_constructor param_types
  195. t = ctor.Invoke(System::Array[Object].new(1) { ts })
  196. t.ApartmentState = System::Threading::ApartmentState.STA
  197. t.Start
  198. end
  199. # Some setup is needed to use WPF from an interactive session console (like iirb). This is because
  200. # WPF needs to do message pumping on a thread, iirb also requires a thread to read user input,
  201. # and all commands that interact with UI need to be executed on the message pump thread.
  202. def self.interact
  203. raise NotImplementedError
  204. def CallBack(function, priority = DispatcherPriority.Normal)
  205. Application.Current.Dispatcher.BeginInvoke(priority, System::Action[].new(function))
  206. end
  207. def CallBack1(function, arg0, priority = DispatcherPriority.Normal)
  208. Application.Current.Dispatcher.BeginInvoke(priority, System::Action[arg0.class].new(function), arg0)
  209. end
  210. dispatcher = nil
  211. message_pump_started = System::Threading::AutoResetEvent.new false
  212. create_sta_thread do
  213. app = Application.new
  214. app.startup do
  215. dispatcher = Dispatcher.FromThread System::Threading::Thread.current_thread
  216. message_pump_started.set
  217. end
  218. begin
  219. app.run
  220. ensure
  221. IronRuby::Ruby.SetCommandDispatcher(None) # This is a non-existent method that will need to be implemented
  222. end
  223. end
  224. message_pump_started.wait_one
  225. def dispatch_console_command(console_command)
  226. if console_command
  227. dispatcher.invoke DispatcherPriority.Normal, console_command
  228. end
  229. end
  230. IronRuby::Ruby.SetCommandDispatcher dispatch_console_command # This is a non-existent method that will need to be implemented
  231. end
  232. class ToFlowDocument
  233. include System::Windows
  234. include System::Windows::Documents
  235. def start_accepting
  236. @@bold_mask = SM::Attribute.bitmap_for :BOLD
  237. @@italics_mask = SM::Attribute.bitmap_for :EM
  238. @@tt_mask = SM::Attribute.bitmap_for :TT
  239. @@hyperlink_mask = SM::Attribute.bitmap_for :HYPERLINK
  240. @@tidylink_mask = SM::Attribute.bitmap_for :TIDYLINK
  241. @flowDoc = FlowDocument.new
  242. @attributes = []
  243. end
  244. def end_accepting
  245. @flowDoc
  246. end
  247. def accept_paragraph(am, fragment)
  248. paragraph = convert_flow(am.flow(fragment.txt))
  249. @flowDoc.blocks.add paragraph
  250. end
  251. def convert_flow(flow)
  252. paragraph = Paragraph.new
  253. active_attribute = nil
  254. flow.each do |item|
  255. case item
  256. when String
  257. case active_attribute
  258. when @@bold_mask
  259. paragraph.inlines.add(Bold.new(Run.new(item)))
  260. @attributes.clear
  261. when @@italics_mask
  262. paragraph.inlines.add(Italic.new(Run.new(item)))
  263. when @@tt_mask
  264. run = Run.new(item)
  265. run.font_family = FontFamily.new "Consolas"
  266. run.font_weight = FontWeights.Bold
  267. paragraph.inlines.add(run)
  268. when nil
  269. paragraph.inlines.add(Run.new(item))
  270. else
  271. raise "unexpected active_attribute: #{active_attribute}"
  272. end
  273. when SM::AttrChanger
  274. on_mask = item.turn_on
  275. active_attribute = on_mask if not on_mask.zero?
  276. off_mask = item.turn_off
  277. if not off_mask.zero?
  278. raise NotImplementedError.new("mismatched attribute #{SM::Attribute.as_string(off_mask)} with active_attribute=#{SM::Attribute.as_string(active_attribute)}") if off_mask != active_attribute
  279. active_attribute = nil
  280. end
  281. when SM::Special
  282. convert_special(item, paragraph)
  283. else
  284. raise "Unknown flow element: #{item.inspect}"
  285. end
  286. end
  287. raise "mismatch" if active_attribute
  288. paragraph
  289. end
  290. def accept_verbatim(am, fragment)
  291. paragraph = Paragraph.new
  292. paragraph.font_family = FontFamily.new "Consolas"
  293. paragraph.font_weight = FontWeights.Bold
  294. paragraph.inlines.add(Run.new(fragment.txt))
  295. @flowDoc.blocks.add paragraph
  296. end
  297. def accept_list_start(am, fragment)
  298. @list = System::Windows::Documents::List.new
  299. end
  300. def accept_list_end(am, fragment)
  301. @flowDoc.blocks.add @list
  302. end
  303. def accept_list_item(am, fragment)
  304. paragraph = convert_flow(am.flow(fragment.txt))
  305. list_item = ListItem.new paragraph
  306. @list.list_items.add list_item
  307. end
  308. def accept_blank_line(am, fragment)
  309. end
  310. def accept_rule(am, fragment)
  311. raise NotImplementedError
  312. end
  313. def convert_special(special, paragraph)
  314. handled = false
  315. SM::Attribute.each_name_of(special.type) do |name|
  316. method_name = "handle_special_#{name}"
  317. return send(method_name, special, paragraph) if self.respond_to? method_name
  318. end
  319. raise "Unhandled special: #{special}"
  320. end
  321. def handle_special_HYPERLINK(special, paragraph)
  322. paragraph.inlines.add(Hyperlink.new(Run.new(special.text)))
  323. end
  324. def handle_special_TIDYLINK(special, paragraph)
  325. text = special.text
  326. # text =~ /(\S+)\[(.*?)\]/
  327. unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
  328. handle_special_HYPERLINK(special, paragraph)
  329. return
  330. end
  331. label = $1
  332. url = $2
  333. hyperlink = Hyperlink.new(Run.new(label))
  334. hyperlink.NavigateUri = System::Uri.new url
  335. paragraph.inlines.add(hyperlink)
  336. end
  337. end
  338. end