這個是百度地圖上北京地鐵的地址http://map.baidu.com/?subwayShareId=beijing,131,我們先看下百度上面的效果圖 我要實現的內容比較簡單,就是繪製這些圖,和在地鐵線上滑動時,會有跟著走的地鐵名的提示。 以下是我實現的,簡陋的效果圖 實現過程: 一、扒數據 我不 ...
這個是百度地圖上北京地鐵的地址http://map.baidu.com/?subwayShareId=beijing,131,我們先看下百度上面的效果圖
我要實現的內容比較簡單,就是繪製這些圖,和在地鐵線上滑動時,會有跟著走的地鐵名的提示。
以下是我實現的,簡陋的效果圖
實現過程:
一、扒數據
我不會爬蟲,所以,這裡是用Fiddler把數據給扒下來,由於北京地鐵官網,最近在維護中,所以進不去,不知道他們是不是提供了相關的數據,如果數據豐富的話,可做的東西還是很多的。
F12打開開發者工具,用滑鼠工具,選擇地鐵圖,會定位到相應的代碼部分
數據就在SVG的結點下麵,我們把數據複製粘貼出來,這些數據上,我們能發現,是分離開的,舉個例子:1號線上有很多個站點,例如蘋果園、古城等,但是在數據上,你看不出1號線和蘋果園、古城的關係所在,也就是說,展示給我們的數據是相對獨立的。數據上,提供了Path的路徑,Textblock和Ellipse的坐標信息。那我們就用這些信息,簡單的畫一下北京地鐵圖。
二、讀取XML文件
將扒下來的數據,放到XML中,稍微修改下,因為會報錯,命名空間的問題,很好弄,就不貼了,觀察數據,以後,建立一個實體DrawInfo,包含幾個欄位
public class DrawInfo { private string type;//類型 : path text ellipse image private string pathData;//path的路徑數據 private string forColor;//顏色 private double x;//坐標 private double y;//坐標 private string tooltip;//ToolTip private string id;//ID private string fontWeight; public string Type { get { return type; } set { type = value; } } public string PathData { get { return pathData; } set { pathData = value; } } public string ForColor { get { return forColor; } set { forColor = value; } } public double X { get { return x; } set { x = value; } } public double Y { get { return y; } set { y = value; } } public string Tooltip { get { return tooltip; } set { tooltip = value; } } public string Id { get { return id; } set { id = value; } } public string FontWeight { get { return fontWeight; } set { fontWeight = value; } } }DrawInfo
下麵是讀取的方法
private void ReadXML() { doc.Load(xmlPath); XmlNodeList xmlNodeList = doc.DocumentElement.GetElementsByTagName("g"); foreach (XmlNode xn in xmlNodeList) { foreach (XmlNode item in xn.ChildNodes) { DrawInfo drawInfo = null; if (item.Name == "path") { drawInfo = new DrawInfo() { Type = "path", PathData = item.Attributes["d"].InnerText, ForColor = item.Attributes["stroke"].InnerText, Tooltip = item.Attributes["lb"].InnerText, }; } if (item.Name == "text") { drawInfo = new DrawInfo() { Type = "text", ForColor = item.Attributes["fill"].InnerText, X = double.Parse(item.Attributes["x"].InnerText), Y = double.Parse(item.Attributes["y"].InnerText), Tooltip = item.InnerText, FontWeight = item.Attributes["font-weight"].InnerText }; } if (item.Name == "ellipse") { drawInfo = new DrawInfo() { Type = "ellipse", ForColor = item.Attributes["stroke"].InnerText, X = double.Parse(item.Attributes["cx"].InnerText) - 6.5, Y = double.Parse(item.Attributes["cy"].InnerText) - 6.5, Tooltip=item.Attributes["name"].InnerText, }; } if (item.Name == "image") { drawInfo = new DrawInfo() { Type = "image", X = double.Parse(item.Attributes["x"].InnerText), Y = double.Parse(item.Attributes["y"].InnerText), Id = item.Attributes["id"].InnerText, Tooltip=item.Attributes["name"].InnerText }; } if (drawInfo != null) DrInList.Add(drawInfo); } } }ReadXML
由於是一個小Demo,所以代碼上寫的不是很規範,例如DrawInfo裡面的欄位,好多,我在後期都公用了,而且定義的類型也不是很規範,用了string,而沒使用Brush、FontWeight等,在取XML結點的Name的時候,也應該定義一個枚舉類型,我都沒有去弄,就是為了寫小例子的時候,省事一點,但是在實際的生產代碼中,建議大家是規範的,利人利己。三、繪製效果圖
數據從XML中拿到以後,繪製效果圖,就方便多了,代碼就很容搞定
private void ShowSubWay() { foreach (DrawInfo item in DrInList) { if (item.Type == "path") { Style style = win.FindResource("pathStyle") as Style; Path path = new Path() { Style = style, Data = Geometry.Parse(item.PathData), Stroke = (Brush)brushConverter.ConvertFromString(item.ForColor), ToolTip = item.Tooltip }; path.MouseMove += Path_MouseMove; path.MouseLeave += Path_MouseLeave; grid.Children.Add(path); } if (item.Type == "text") { Style style = win.FindResource("textblockStyle") as Style; TextBlock textBlock = new TextBlock() { Style = style, Foreground = (Brush)brushConverter.ConvertFromString(item.ForColor), Margin = new Thickness() { Left = item.X, Top = item.Y }, Text = item.Tooltip, FontWeight = (FontWeight)new FontWeightConverter().ConvertFromString(item.FontWeight), }; grid.Children.Add(textBlock); } if (item.Type == "ellipse") { Style style = win.FindResource("ellipseStyle") as Style; Ellipse ellipse = new Ellipse() { Style = style, Stroke = (Brush)brushConverter.ConvertFromString(item.ForColor), Margin = new Thickness() { Left = item.X, Top = item.Y }, ToolTip=item.Tooltip }; ellipse.MouseMove += Ellipse_MouseMove; ellipse.MouseLeave += Ellipse_MouseLeave; grid.Children.Add(ellipse); } if (item.Type == "image") { Style style = null; if (item.Id == "svgjsImage12298" || item.Id == "svgjsImage12302") style = win.FindResource("imgAir") as Style; else style = win.FindResource("imgTran") as Style; Image img = new Image() { Style = style, Margin = new Thickness() { Left = item.X, Top = item.Y }, ToolTip=item.Tooltip }; img.MouseMove += Img_MouseMove; img.MouseLeave += Img_MouseLeave; grid.Children.Add(img); } } }ShowSubWay
代碼很Low,羽神說,我把WPF寫成了Winform,不得不承認,是這個樣子的,binding什麼的,都沒用,但是後來想了想不是我不想用,而是地鐵圖的路線不規則,沒有規律,我就不知道怎麼重寫一個控制項來綁定一個List集合,統一進行展示了,思考了挺久,但是依然沒有好的想法,因為,我們在公司做的binding,都是基於數據層面的,說白了,就是前臺的XAML變動不大,或者變動的比較有規律,例如重寫Listbox的Template,來實現統一的效果,綁定數據以後,自動展示。
就像上面這個圖,就可以重寫Listbox,然後實現效果,但是我這次的小Demo里沒有做,因為XML的數據中沒有提供相關的東西,也就沒有去搞了,相信,做過WPF的,看了這個圖,基本也就知道怎麼搞了,如果有沒有思路的朋友,可以留言問我。
四、類似於ToolTip的地鐵名提示
這個功能特別的感謝李偉哥@普通的地球人,不得不說,李偉哥的WPF特別厲害,輕鬆的搞定了我當時出現的困擾。最開始做跟著滑鼠走的ToolTip的時候,首先想到的是ToolTip,但是發現,不好用,因為他會定位到一個位置,不會跟著走,後來也用了Popup,但是根據網上的方法,會閃爍,因為Popup是需要先隱藏,再顯示的,所以,有閃爍的效果,在群里問了以後,李偉哥很快給了一個Demo,解答了我,厲害,在此謝謝李偉哥。
<Popup Grid.Row="1" x:Name="popPath" AllowsTransparency="True"> <Border BorderBrush="Transparent" BorderThickness="0" Background="Transparent"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center"> <Path Stroke="Black" StrokeThickness="1" Fill="White" Data="M 0,0 L 120,0 L 120,30 L 65,30 L 60,35 L 55,30 L 0,30 Z" /> <Grid x:Name="grpopPath" Margin="5,5,5,10"> <TextBlock x:Name="tbPath" Foreground="White" FontSize="13" HorizontalAlignment="Center" VerticalAlignment="Center" Style="{Binding Null}" /> </Grid> </Grid> </Border> </Popup>
private void Path_MouseMove(object sender, MouseEventArgs e) { Path path = (Path)sender; grpopPath.Background = path.Stroke; tbPath.Text = path.ToolTip.ToString(); Point p = e.GetPosition(mainGrid); popPath.IsOpen = true; popPath.HorizontalOffset = p.X - 60; popPath.VerticalOffset = p.Y - mainGrid.ActualHeight - 40; } private void Path_MouseLeave(object sender, MouseEventArgs e) { popPath.IsOpen = false; }
效果如圖
五、擴展
1、Grid
這個控制項,我一直使用,也習慣了用Grid,但是在做的過程中,確確實實學習到了不少東西,在此感謝羽神(不知道羽神的博客園地址),問題是這樣的,就是繪製Path和Textblock的時候,都沒有問題,但是繪製Ellipse的時候,卻出現問題了,定位的位置不對,嚴重便宜到右下方,但是也設置了Grid的HorizontalAlignment="Left" VerticalAlignment="Top",但是都不起作用。
羽神一語道破,是因為Grid的內部控制項,都是預設全部填充的,需要設置控制項自身的HorizontalAlignment和VerticalAlignment,當時設置完以後,就搞定了這個問題,感謝羽神的幫助。
2、Canvas
群里有人問,為什麼不用Canvas而用Grid,我當時確實沒有考慮過用Canvas,說白了,我不太習慣除非必要,我個人喜歡用Grid,所以,當時給出的回答是“沒想那麼多”,但是當做完以後,後來想想,改成Canvas試下效果,但是發現了原因,此處,還真不能用Canvas,需要用Grid,因為我的外層用了Viewbox,因為百度地圖上趴下了的數據,Path的路徑和定位都特別的大,都是2000多,3000多大的樣子,用了Canvas,沒法顯示下。可能大家不太好理解,我舉個例子:外層用Viewbox,窗體的大小是300*300的,Viewbox下我們測試Canvas和Grid,Canvas和Grid下,放置一個3000*3000的Button。
上面是Canvas的例子代碼和效果,明細可以看到,展示不出來Button,就是最大化也不行,我的電腦屏幕比較小。
上面是Grid的例子,能夠看出,Button是可以顯示出來的,填充了整個Viewbox,這和地鐵圖的原理是一樣的。
有人可能會問,Canvas如果不給寬和高呢,如果Canvas不給寬和高,則會在左側頂點上,你可以試驗下,就知道了。
六、總結
這個例子用到的知識點:XML的讀取,控制項的拖拽縮放,內容不多,但是,與我個人而言,每次做個小Demo都有所收穫和成長,因為公司不忙,所以不想手生了,只能沒事做個小Demo,補充自己的不足,提高自己,就像做的時候,遇到的兩個問題,李偉哥和羽神給了很好的回答,我都學習到了,謝謝。