最近公司用SharpMap做了一個做桌面程序,它是一個開源的Gis項目,功能還可以,最大的特點就是簡單易用,這里介紹下怎么在web下使用:
這次我們根據demo先了解一下如何show一個地圖。這是最基本的步驟,也比較簡單,希望能對剛入門的同學有所幫助。
我們使用SharpMap.UI.dll中的ajax控件 <smap:AjaxMapControl width="1600px" Height="600px" id="ajaxMap" runat="server"
OnClickEvent="MapClicked" onmouseout="toolTip();"; OnViewChange="ViewChanged" CssClass="Ly" UseCache="false"
OnViewChanging="ViewChanging" ZoomSpeed="1" /> 來展示地圖,主要代碼分析如下:
首先,初始化地圖
public static SharpMap.Map InitializeMap(System.Drawing.Size size) { HttpContext.Current.Trace.Write("Initializing map"); //Initialize a new map of size 'imagesize' SharpMap.Map map = new SharpMap.Map(size); //Set up the countries layer SharpMap.Layers.VectorLayer layCountries = new SharpMap.Layers.VectorLayer("Countries"); //Set the datasource to a shapefile in the App_data folder layCountries.DataSource = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\countries.shp"), true); //Set fill-style to green layCountries.Style.Fill = new SolidBrush(Color.Green); //Set the polygons to have a black outline layCountries.Style.Outline = System.Drawing.Pens.Black; layCountries.Style.EnableOutline = true; layCountries.SRID = 4326; //Set up a river layer SharpMap.Layers.VectorLayer layRivers = new SharpMap.Layers.VectorLayer("Rivers"); //Set the datasource to a shapefile in the App_data folder layRivers.DataSource = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\rivers.shp"), true); //Define a blue 1px wide pen layRivers.Style.Line = new Pen(Color.Blue,1); layRivers.SRID = 4326; //Set up a river layer SharpMap.Layers.VectorLayer layCities = new SharpMap.Layers.VectorLayer("Cities"); //Set the datasource to a shapefile in the App_data folder layCities.DataSource = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\cities.shp"), true); //Define a blue 1px wide pen //layCities.Style.Symbol = new Bitmap(HttpContext.Current.Server.MapPath(@"~\App_data\icon.png")); layCities.Style.SymbolScale = 0.8f; layCities.MaxVisible = 40; layCities.SRID = 4326; //Set up a country label layer SharpMap.Layers.LabelLayer layLabel = new SharpMap.Layers.LabelLayer("Country labels"); layLabel.DataSource = layCountries.DataSource; layLabel.Enabled = true; layLabel.LabelColumn = "Name"; layLabel.Style = new SharpMap.Styles.LabelStyle(); layLabel.Style.ForeColor = Color.White; layLabel.Style.Font = new Font(FontFamily.GenericSerif, 12); layLabel.Style.BackColor = new System.Drawing.SolidBrush(Color.FromArgb(128,255,0,0)); layLabel.MaxVisible = 90; layLabel.MinVisible = 30; layLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center; layLabel.SRID = 4326; layLabel.MultipartGeometryBehaviour = SharpMap.Layers.LabelLayer.MultipartGeometryBehaviourEnum.Largest; //Set up a city label layer SharpMap.Layers.LabelLayer layCityLabel = new SharpMap.Layers.LabelLayer("City labels"); layCityLabel.DataSource = layCities.DataSource; layCityLabel.Enabled = true; layCityLabel.LabelColumn = "Name"; layCityLabel.Style = new SharpMap.Styles.LabelStyle(); layCityLabel.Style.ForeColor = Color.Black; layCityLabel.Style.Font = new Font(FontFamily.GenericSerif, 11); layCityLabel.MaxVisible = layLabel.MinVisible; layCityLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Left; layCityLabel.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Bottom; layCityLabel.Style.Offset = new PointF(3, 3); layCityLabel.Style.Halo = new Pen(Color.Yellow, 2); layCityLabel.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias; layCityLabel.SmoothingMode = SmoothingMode.AntiAlias; layCityLabel.SRID = 4326; layCityLabel.LabelFilter = SharpMap.Rendering.LabelCollisionDetection.ThoroughCollisionDetection; layCityLabel.Style.CollisionDetection = true; //Add the layers to the map object. //The order we add them in are the order they are drawn, so we add the rivers last to put them on top map.Layers.Add(layCountries); map.Layers.Add(layRivers); map.Layers.Add(layCities); map.Layers.Add(layLabel); map.Layers.Add(layCityLabel); //limit the zoom to 360 degrees width map.MaximumZoom = 360; map.BackColor = Color.LightBlue; map.Zoom = 360; map.Center = new SharpMap.Geometries.Point(0,0); HttpContext.Current.Trace.Write("Map initialized"); return map; }
這段代碼中,
SharpMap.Layers.VectorLayer layCountries = new SharpMap.Layers.VectorLayer("Countries");
layCountries.DataSource = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\countries.shp"), true);
這兩句話定義並初始化了一個map的一個層。一個map可能包含多個層,我們對map進行操作,其實就是對map的各個層進行操作,比如說道路層,建築層等等。根據業務的復雜程度,可能有很多個層。一般來說,每個層都有各自的抽象特征(一類對象),可以進行一樣的操作。各個層組合起來,就是一個map.
好,接着說初始化。這里為VectorLayer的構造函數提供了一個層的名稱,然受設置了數據源。一個層的Datasource是一個IProvider類型的數據,而ShapeFile類正是實現了Iprovider接口的類。(這點很重要,以后有很多機會需要把一個datasource轉換成ShapeFile)。
接下來是對這個層進行着色,呵呵 就是這個層上的對象如何填充顏色。這里因為是剛剛開始,我們就用最簡單的辦法,設置了一個
layCountries.Style.Fill = new SolidBrush(Color.Green);
這樣,就把層上的對象都填充成了綠色。在以后的講解中,我們會讓大家了解到如何自定義theme,這樣我們能根據實現設置的對象的類型填充不同的顏色,以實現自定義的樣式,讓地圖多彩多姿。
然后我們看到label層。每一個map的layer都有自己的label層。
SharpMap.Layers.LabelLayer layLabel = new SharpMap.Layers.LabelLayer("Country labels");
layLabel.DataSource = layCountries.DataSource;
layLabel.Enabled = true;
layLabel.LabelColumn = "Name";
這里我們首先定義了一個label層,然后把countries層的datasource直接賦給label層的datasource就OK了。這樣,這個新定義的label層就屬於countries層了。然后我們給它制定一個labelColumn,這個是必須指定的。它表示countries層的每個對象應該顯示什么內容。這個內容在ShapeFile(我們在MapWindow GIS中建立的shapefile文件)的attribute中進行增加和更新。
我想有必要說一下地圖的對象。
地圖上有很多點,線和多邊形。這些都是對象。我們都可以對他們進行操作。每個對象有相應的屬性,這些屬性我們也可以添加,修改和刪除。而上述的label的LabelColumn就是這些屬性中的一個。我們在這里可以指定一個,也可以通過LabelStringDelegate來委托一個方法返回一個字符串(這個將在以后進行詳細講解)。
OK,在設置了label的字體顏色,大小和背景色之后,有兩個個屬性是特別值得我們注意的。
layLabel.MaxVisible = 90;
ayLabel.MinVisible = 30;
這兩個值說明了我們在多大/多小的比例尺下可以看到這個label層。我就經常會忽略這個問題,以至於在放大或縮小比例尺的時候看不到label,也找不到原因。
在初始化的最后,我們把所有的層都加到地圖中來,在設置一下最大的比例尺,背景色和中心點,就可以了
map.Layers.Add(layCountries);
map.Layers.Add(layRivers);
map.Layers.Add(layCities);
map.Layers.Add(layLabel);
map.Layers.Add(layCityLabel);
//limit the zoom to 360 degrees width
map.MaximumZoom = 360;
map.BackColor = Color.LightBlue;
最后,我們通過一個maphandler(ashx)把地圖輸出到頁面上。
System.Drawing.Bitmap img = (System.Drawing.Bitmap)map.GetMap();
context.Response.ContentType = "image/png";
System.IO.MemoryStream MS = new System.IO.MemoryStream();
img.Save(MS, System.Drawing.Imaging.ImageFormat.Png);
// tidy up
img.Dispose();
byte[] buffer = MS.ToArray();
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
代碼都來自www.codeplex.com/sharpmap的demo。
添加主題 更具一些特性:
private VectorStyle GetCountryStyle(FeatureDataRow row) { VectorStyle style = new VectorStyle(); switch (row["NAME"].ToString().ToLower()) { case "denmark": //If country name is Danmark, fill it with green style.Fill = Brushes.Green; return style; case "united states": //If country name is USA, fill it with Blue and add a red outline style.Fill = Brushes.Blue; style.Outline = Pens.Red; return style; case "china": //If country name is China, fill it with red style.Fill = Brushes.Red; return style; default: break; } //If country name starts with S make it yellow if (row["NAME"].ToString().StartsWith("S")) { style.Fill = Brushes.Yellow; return style; } // If geometry is a (multi)polygon and the area of the polygon is less than 30, make it cyan else if (row.Geometry is IMultiPolygon && (row.Geometry as IMultiPolygon).Area < 30 || row.Geometry is IPolygon && (row.Geometry as IPolygon).Area < 30) { style.Fill = Brushes.Cyan; return style; } else //None of the above -> Use the default style return null; }
在經過第一篇的簡單學習之后,我們開始了解一些稍微有點兒意思的東西,進一步掌握和學習利用sharpmap進行開發的技巧。
這次,我們主要是跟大家一起學習一下如何根據地圖上的一個點,來查詢這個點所在的對象的信息,並顯示到點擊的位置。這非常有用,比如說一個想把一個房子顯示在地圖上,我們用鼠標一點,便知道這個房子里住的什么人,干什么的,以及其它相關信息。
同樣的,我們還是使用sharpmap提供的ajax控件,環境和第一篇一模一樣。但是這里,我們要引用一個叫做NetTopologySuite的類庫。它經常和SharpMap一起使用,這次我們只使用其中的一個小部分,廢話不多說,開始做。
這里我們使用asp.net ajax 1.0,首先引用了dll,並且拖上scripmanager並設置為EnablePageMethods=true,這樣我們就可以在頁面中寫靜態方法來實現AJAX。
在MapClicked方法(AjaxMapControl控件提供的方法,直接寫在js中即可,表示單擊的時候發生一些事情)中,我們調用我們寫的js,根據兩個點來返回一個字符串。這個字符串就是拼好的html,直接顯示出來。









這個方法里面的SharpMap_GetRelatiovePosition和 SharpMap_PixelToMap方法根據鼠標在屏幕上的坐標計算出鼠標點在地圖上的坐標,再調用我們自己寫的GetData方法即可。在GetData方法中,我們使用了PageMethods來調用一個后台方法,並返回一個字符串。
PageMethods.GetData(x, y, GetDataSuccess);
然后,在GetDataSuccess中調用一個寫好的用來顯示這些返回數據的方法(此方法來自網絡,js源碼如下)。

<script type="text/javascript">
<!--
var ns4 = document.layers;
var ns6 = document.getElementById && !document.all;
var ie4 = document.all;
offsetX = 0;
offsetY = 20;
var toolTipSTYLE = "";
function initToolTips() {
if (ns4 || ns6 || ie4) {
if (ns4) toolTipSTYLE = document.toolTipLayer;
else if (ns6) toolTipSTYLE = document.getElementById("toolTipLayer").style;
else if (ie4) toolTipSTYLE = document.all.toolTipLayer.style;
if (ns4) document.captureEvents(Event.MOUSEMOVE);
else {
toolTipSTYLE.visibility = "visible";
toolTipSTYLE.display = "none";
}
document.onclick = moveToMouseLoc;
}
}
function toolTip(msg, fg, bg) {
if (toolTip.arguments.length < 1) // hide
{
if (ns4) toolTipSTYLE.visibility = "hidden";
else toolTipSTYLE.display = "none";
}
else // show
{
if (!fg) fg = "#777777"; //fore color
if (!bg) bg = "#FFFFFF"; //bg color
var content = msg;
if (ns4) {
toolTipSTYLE.document.write(content);
toolTipSTYLE.document.close();
toolTipSTYLE.visibility = "visible";
}
if (ns6) {
document.getElementById("toolTipLayer").innerHTML = content;
toolTipSTYLE.display = 'block'
}
if (ie4) {
document.all("toolTipLayer").innerHTML = content;
toolTipSTYLE.display = 'block'
}
}
}
function moveToMouseLoc(e) {
if (ns4 || ns6) {
x = e.pageX;
y = e.pageY;
}
else {
x = event.x + document.body.scrollLeft;
y = event.y + document.body.scrollTop;
}
toolTipSTYLE.left = x + offsetX;
toolTipSTYLE.top = y + offsetY;
return true;
}
//-->
</script>
需要指出的是,這個效果需要做一些初始化的工作:
<div id="toolTipLayer" style="position:absolute;visibility: hidden;z-index:10000">
</div>
<script type="text/javascript">
initToolTips();
</script>
這樣,我們在前台的工作就基本完成了。
當然,如果想要更好看一些,再寫一個css幫助從后台傳來的html進行淡化。
.clarity
{
filter:alpha(opacity=80,Style=0);
}
OK,現在轉去后台。
在根據兩個點查詢對象數據時,我們首先要初始化需要初始化shapefile,也就是你要查詢的那個層的數據源。
new SharpMap.Data.Providers.ShapeFile(filepath true);
然后遍歷shapefile里面的每一個feature,對比傳入的點和feature所在的Geometry,如果傳入的點在feature所在Geometry之內(within方法)的話,就讀取當前feature的一個attribute(通常是某個業務對象的ID),這樣,根據這個ID就能取到業務對象的值,然后Build一下HTML,返回就OK了。主要代碼如下:

public string GetInfo(double x, double y)
{
SharpMap.Data.Providers.ShapeFile oShape=(SharpMap.Data.Providers.ShapeFile)GetShapeFile(filepath);
if (!oShape.IsOpen)
{
oShape.Open();
}
uint iFeature = (uint)oShape.GetFeatureCount();
SharpMap.Geometries.Point oPoint = new SharpMap.Geometries.Point(x, y);
GisSharpBlog.NetTopologySuite.Geometries.Geometry oPt = SharpMap.Converters.NTS.GeometryConverter.ToNTSGeometry(oPoint, new GisSharpBlog.NetTopologySuite.Geometries.GeometryFactory());
StringBuilder sb = new StringBuilder();
// find a record
string graveInfo = string.Empty;
for (uint i = 0; i < iFeature; i++)
{
GisSharpBlog.NetTopologySuite.Geometries.Geometry oPoly = SharpMap.Converters.NTS.GeometryConverter.ToNTSGeometry(oShape.GetFeature(i).Geometry, new GisSharpBlog.NetTopologySuite.Geometries.GeometryFactory());
if (oPt.Within(oPoly))
{
if (oShape.GetFeature(i)[someId].ToString().Length > 0)
{
return BuildGraveInfoTable(name, value);
}
}
}
return string.emtpy;
}
這里把sharpfile的Geometry都轉換成了NTS 的,這樣,使用NTS的Within方法進行比較。sharpmap自己的方法在我的測試中是不能用的,希望大家在使用過程中做些嘗試,看看是否是我自己的代碼有問題,總之,我是用的NTS。
到這里,我們就實現了根據點來查詢對象數據的功能,我們只需要在shapefile中存儲一個attribute(比如對象的ID),然后取出來顯示出去就OK了。
同理的,我們也可以實現根據一個用戶ID來找到這個對象在地圖中的位置,並顯示在地圖中間。
代碼如下:

public SharpMap.Geometries.Point GetPointByID(string Id)
{
SharpMap.Data.Providers.ShapeFile oShape = (SharpMap.Data.Providers.ShapeFile)GetShapeFile(filePath);
oShape.Open();
uint i = 0;
uint iFeature = (uint)oShape.GetFeatureCount();
SharpMap.Geometries.Point oPt = null;
for (i = 0; i < iFeature; i++)
{
GisSharpBlog.NetTopologySuite.Geometries.Geometry oPoly = SharpMap.Converters.NTS.GeometryConverter.ToNTSGeometry(oShape.GetFeature(i).Geometry, new GisSharpBlog.NetTopologySuite.Geometries.GeometryFactory());
if (oShape.GetFeature(i)["Id"].ToString() == Id)
{
oPt = new SharpMap.Geometries.Point(oPoly.InteriorPoint.X, oPoly.InteriorPoint.Y);
}
}
return oPt;
}
找到這個點之后,設置一下控件的地圖的中心點,就OK了。
ajaxMap.Map.Center = pCenter;
雖然東西很簡單,但是還是費了我很多功夫。很大一部分原因是因為sharpmap自己提供的方法不太完善,導致很多時間花費在了追蹤問題上。不過這種追蹤對理解開源的代碼也是很有好處的。
在這個例子中,我使用了遍歷地圖中所有feature的方法來定位一個對象,這樣非常的耗費資源,效率並不好。如果地圖對象比較少還可以,一旦超過一定的數量級,可能會出現性能問題。以后我會在這個基礎上進行改進,利用其它方法來實現這個功能(事實上我已經進行了一些嘗試,只是還沒有成功。如果有人成功了,希望給我一些建議,謝謝)。
今天就到這里,感謝大家的關注,希望多多拍磚,共同進步。
在下一篇中,我將和大家共同學習關於根據attribute的值來填充地圖上對象的顏色,設置他們的樣式以及一些有趣的小玩意兒。希望大家繼續關注,謝謝。
每天學一天,每天都會進步一點兒。
我寫的東西內容淺顯,希望能給初學者一些幫助。至於深入研究sharpmap和GIS技術的大牛,請不吝賜教,給我們這些菜鳥多一些指導。
今天我們接着來聊sharpmap的基本使用技巧,根據attribute來填充地圖對象的顏色,讓用戶更清晰的看到重點的業務對象對應在地圖上的表示,以及如何自定義label層的顯示內容,字體的大小等。所以,今天的主題主要是自定義:自定義theme,自定義label以及label字體。
首先,我們要為地圖填充上不同的色彩,讓他們看起來五顏六色,容易分辨。比如河流和湖泊要填成藍色,草地要填充上綠色,房子要填充上白色,道路要填充上青色等等。怎么做呢?很簡單,先看下代碼:
SharpMap.Rendering.Thematics.CustomTheme iTheme = new SharpMap.Rendering.Thematics.CustomTheme(GetMyStyle);
在初始化Map的時候,加上上面的一行代碼。它定義了一個自定義的Theme對象,這個對象的構造函數需要傳入一個我們自己寫的方法(委托),這個方法里面具體說明了這個 theme是如何定義的,方法代碼如下:
{
SharpMap.Styles.VectorStyle style = new SharpMap.Styles.VectorStyle();
switch (row["Status"].ToString().ToLower())
{
case "available": //If status is interred, fill it with yellow
style.Fill = Brushes.Yellow;
return style;
default:
style.Fill = Brushes.Green;
return style;
}
}
這段簡單代碼大家都看得懂,這個委托定義需要傳入一個FeatureDataRow,它的作用就是在初始化地圖的過程中,每處理一個對象,都要到這個方法中來考察一下feature的值是多少以決定具體要填充哪些顏色。在這里,當status為available時,就填充黃色,否則就填充綠色。定義的VectorStyle用來返回給地圖。是不是很簡單呢?
地圖填充完了,我們要在上面顯示出到底他們是什么。在第一篇中,我們已經可以把對象的一個屬性顯示出來,但是當地圖擴大到一定比例時,僅僅顯示名字這一種信息已經不足以滿足用戶的需要了。比如說一棟房子,在很遠處看它時,我們只需要看到他的顏色就行了。但是當我們離的很近的時候,難道看到的是全白色嗎?顯然不是,我們還可以看到門牌號,窗戶上的貼紙以及其它詳細信息。地圖也一樣,當比例尺改變以后,應該有更詳細的信息被列出來。老規矩,先貼代碼:
SharpMap.Layers.LabelLayer myLabel = new SharpMap.Layers.LabelLayer("labels");
myLabel .DataSource = myLayer.DataSource;
myLabel.LabelStringDelegate= new SharpMap.Layers.LabelLayer.GetLabelMethod(GetLabelString);
在這里,首先定義了一個label(就像在初始化地圖里一樣,或者就在初始化地圖里做),然后把你所要顯示的圖層的數據源賦值給這個label的數據源,接着為label層的LabelStringDelegate指定一個方法(這個方法傳入一個FeatureDataRow,返回一個字符串),最后去實現這個方法,代碼如下:
public static string GetLabelString(FeatureDataRow dr)
{
string field1= string.Empty;
string field2= string.Empty;
string field13= string.Empty;
if (dr["HouseNumber"] != null)
{
field1= dr["HouseNumber"].ToString();
}
if (dr["HostName"] != null)
{
field2= dr["HostName"].ToString();
}
if (dr["Memo"] != null)
{
field3= dr["Memo"].ToString();
}
StringBuilder sb = new StringBuilder();
sb.Append(field1);
sb.Append(": ");
sb.Append(field2);
sb.Append("\r\n" + field3);
return sb.ToString();
}
代碼簡單的要死,不再詳述,值得注意的是:如果要換行,請使用“\r\n”, 這里不支持html拼的字符串。哈,又解決一個問題。爽吧?
然后,新的問題又來了,字體一直都是那么大,或者一直都是那么小,這可怎么辦呢?看起來也相當不舒服啊。如果字體能隨着比例尺的變化而變化就好了。這個沒問題,我們馬上就搞定它。看代碼:
public void ChangeFontSize(double Zoom, SharpMap.Layers.ILayer iLayer)
{
SharpMap.Layers.LabelLayer labelLayer = (SharpMap.Layers.LabelLayer)iLayer;
if (Zoom < 800 && Zoom > 400)
{
((SharpMap.Layers.LabelLayer)labelLayer).Style.Font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif, 6);
}
else if (Zoom < 400 && Zoom > 140)
{
((SharpMap.Layers.LabelLayer)labelLayer).Style.Font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif, 8);
}
else if (Zoom < 140.00 && Zoom > 20)
{
((SharpMap.Layers.LabelLayer)labelLayer).Style.Font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif, 16);
}
else if (Zoom < 20.00)
{
((SharpMap.Layers.LabelLayer)labelLayer).Style.Font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif, 48);
}
else
{
((SharpMap.Layers.LabelLayer)labelLayer).Style.Font = new System.Drawing.Font(System.Drawing.FontFamily.GenericSerif, 70);
}
}
這個方法傳入了一個Zoom值和一個ILayer,沒有返回值,直接修改ILayer就可以了。
相信大家都看得懂吧?值得注意的問題是:AjaxMap上面的map對象有個屬性叫做zoomAmount,這是個javascript的屬性需要在前台指定,它說明了地圖放大或縮小的比例尺倍數。Demo中是在RadioButtonList的ListItem中指定的。這個過程是這樣的:當點擊Zoom In這個button,自動執行了一段javascript:
ajaxMapObj.disableClickEvent(); ajaxMapObj.zoomAmount = 3;
然后當我們點擊地圖,click事件已經被禁止,系統刷新地圖,此時在handler里接受到了地圖的新的參數(zoom等),然后重新繪制了地圖。所以,我們在handler里取得zoom參數,並且使用GetLayerByName(“”)方法來取得label層
ChangeFontSize(Zoom, map.GetLayerByName("mylabels"));
這樣,就實現了字體隨着比例尺變化兒變化。有同學可能問:那如何讓label的內容隨着比例尺變化而變化呢?只需要把剛才那句話
myLabel.LabelStringDelegate= new SharpMap.Layers.LabelLayer.GetLabelMethod(GetLabelString);
放到相應的if。。。else語句里就行了。簡單吧?
今天就到這里。
這些都是些小技巧,在大牛眼里也許不值一提,但是我希望能給初學者帶來一些幫助,希望大家工作愉快,天天好心情。
最后歡迎拍磚。