前言: 在項目開發的過程之中,經常需要做IO監控畫面.當IO監控點多的時候,往往需要做很多的畫面,並且浪費了HMI的很多IO點.做起畫面來也很麻煩和繁瑣.從而,讓我思考着如何做一個工具.可以在一個畫面上監控所有的變量.
要實現這個,其實也不難.
1,需要將IO變量泛型的通過指針的方式,反饋到一個變量上,並且使用這個變量的位來指示每個IO的狀態.
2,需要將IO的文本解釋生成一系列的IO圖片,然后在HMI上建立IO列表.關聯起來.
一,使用C#開發圖片生成工具:
在左側輸入,I的文本解析,在右側輸入O的文本解析.在IO地址處放入起始地址.然后點擊生成圖片,就會生成一系列的圖片了.
300表示從I300.0和Q300.0開始的IO變量.點擊生成圖片,開始生成:
打開一張圖片,看下效果:名字叫 IO_0.PNG
最后一張 名字叫 IO_15.PNG
建立完之后,把所有圖片拷貝項目文件夾里面,並且建立圖形列表,名字叫IOtable
再建立一個IO點滅和亮通過不同形式表示的圖形列表,IO顯示(反正大家可以自己去設)
做完之后,再新建一個畫面
這是仿真的,實際畫面里面是 一個圖形IO,綁定,剛才建立的IOTable圖形列表.以及32個圖形IO,綁定剛才建立的IO顯示圖形列表
外加兩個翻頁按鈕,當按左邊時候,循環向下翻頁,按右邊時循環向上翻頁(翻到底時就重復到起始地址).
這是翻到第一頁.可以看到,第一個I300.0沒有被點亮,而第二頁,則被點亮了.
實際上是連接了一個變量:
二,我們在PLC里面來實現這個功能:新建一個FB塊,名字叫IO_Monitor
輸入接口說明:
- Random:如果為true,則表示是無序IO,需要人工設定IOTable表,以綁定每個Index對應的IO地址.
如果為False,則自動建立IO地址和Index之間的關系.默認是自動建立:
IF NOT #Random AND "FirstScan" THEN
FOR #i := 0 TO #LastIndex DO
#IOTable[#i] := #StartAddr + #i * 2;//因為地址是字節為單位,Index是以字為單位.IOTable[i],表示當前索引對應的IO地址.
END_FOR; //StartAddr ---還記得嗎?就是上面的開始地址.也就是支持偏置.
END_IF;
- LastIndex:表明最終索引號,本列是15.表示有0-15,16個圖片,共16*16,200多I點,和200多O點
- StartAddr:已經說明.根據實際IO地址填入.
- IOInterface.Left_Sw: 左翻頁,實際就是把Index-1
- IOInterface.Right_Sw:右翻頁,實際就是把Index+1
- 為防止Index<0或>LastIndex,我做了個處理,當其為0-1時則其=最大值,反之LastIndex+1時,為最小值.
- IOStatus,為當前索引的IO狀態.
- IOIndex為當前索引.
實現:
通過 除以2 來找到在Word數組中的位置. 通過Move_BLK_VAriant函數,來將本來不序列化的Any類型轉為了一個WOrd數組.
也就是 P#I0.0 Word 1000,實際上變成了一個 數組 tmp[0..999] of Word -----其中,tmp[0]=IW0,tmp[1]=IW2,tmp[999]=Iw1998.---------從而,也理解了剛才除以2的意義.因為,當我尋址I300時候,實際上在數組中,是tmp[150].
這是翻頁的實現:
這是CircleMode,其用處是將任意值取模到指定范圍內.比如 –1 ,取模后變成了15.16,取模后變成了0.
二,下面講一下利用C#實現自動生成圖片的工具制作方法.
1,首先建立操作界面:
<Window x:Class="EsayTools.IoTable" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:EsayTools" mc:Ignorable="d" Title="IO圖片生成器" Height="493.31" Width="1293.16" FontSize="20" WindowStyle="ToolWindow"> <Grid > <Grid.RowDefinitions> <RowDefinition Height="40"></RowDefinition> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="40"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="輸入點文本" VerticalAlignment="Center" Margin="20 0 0 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock> <TextBlock Grid.Column="1" Text="輸出點文本" VerticalAlignment="Center" Margin="20 0 0 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock> <TextBox x:Name="Txt1" Grid.Row="1" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Background="AliceBlue" BorderThickness="3" BorderBrush="Black" Margin="2"></TextBox> <TextBox x:Name="Txt2" Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" AcceptsReturn="True" VerticalScrollBarVisibility="Auto" Background="AliceBlue" BorderThickness="3" BorderBrush="Black" Margin="2"></TextBox> <TextBlock Text="IO起始地址:" Grid.Row="2" VerticalAlignment="Center" HorizontalAlignment="Right" Margin="20 0 20 0" FontSize="20" FontStyle="Italic" Foreground="Red"></TextBlock> <Grid Grid.Column="3" Grid.Row="3"> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <TextBox x:Name="IOAddr" BorderBrush="Black" BorderThickness="3" Background="AliceBlue" ></TextBox> <Button Click="bt1_Click" Grid.Column="2" Margin="10 2 10 2" FontSize="20">生成圖片</Button> </Grid> </Grid> </Window>
結果:
其次:建立界面效果圖:
由於在一個TextBlock中同時顯示IO,存在一個設計數據對齊的問題.也就是說,比如一個漢字對應2個字母,對應3個空格等等.
最后解決辦法,將字體敢為SimSun---憑借我不怎么好的英語瞎拆下是不是"仿宋"的意思??
對齊的算法:
private string FormatStringToChina(string str, int len) { return str + new string(' ', len - Encoding.GetEncoding("gb2312").GetBytes(str).Length); }
len:為要對齊的最大長度.(在這里,有些字體是不行的...)
首先將寫入操作界面的字符串轉為字符串數組:
string[] strIs = ParentTxt1.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); string[] strOs = ParentTxt2.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); var Len = ((strIs.Length+15) / 16)> ((strOs.Length + 15 )/ 16)?((strIs.Length + 15 )/ 16): ((strOs.Length + 15 )/ 16); int StartAddr = ParentAddr; ; //檢查文本框 if (strIs.Length == 0 && strOs.Length == 0) { System.Windows.Forms.MessageBox.Show("請填入正確的數據,IO文本數量不匹配"); return; } if(ParentOutputFile==null) { System.Windows.Forms.MessageBox.Show("未輸入正確文件名"); return; }
其次,生成IO字符串數組
private void generateStrings(int addr, out string[] iaddrs, out string[] oaddrs) { iaddrs = new String[16]; oaddrs = new string[16]; for (int i = 0; i < 16; i++) { iaddrs[i] = "I" + (addr + i / 8) + "." + (i % 8); oaddrs[i] = "Q" + (addr + i / 8) + "." + (i % 8); } }
然后迭代 刷新TextBlock並且生成圖片:
刷新TextBlock代碼:
public void GenerateItem(TextBlock textBlock, string Iaddr, string Itxt, string Oaddr, string Otxt) { if(Itxt==null || Itxt==String.Empty) { Itxt ="備用"; } if (Otxt == null || Itxt == String.Empty) { Otxt = "備用"; } textBlock.Text = " " + FormatStringToChina(Iaddr, 10) + FormatStringToChina(Itxt, 40) + FormatStringToChina(Oaddr, 10) + FormatStringToChina(Otxt, 40); if(IsBlue) { textBlock.Background = Brushes.Orange; IsBlue = false; } else { textBlock.Background = Brushes.Brown; IsBlue = true; } } public void GenerateItems(String[] Inops, String[] Onops, string[] Itxts, string[] Otxts) { int Index = 0; foreach (var Item in grid1.Children) { TextBlock textBlock = (TextBlock)Item; GenerateItem(textBlock, Inops[Index], Itxts[Index], Onops[Index], Otxts[Index]); Index++; } }
生成圖片代碼(網上找的):用於將Visual控件的內容轉為圖片,我這邊是grid1,也就是包含了16個block的一個布局面板.
private void GetPicFromControl(FrameworkElement element, String type, String outputPath) { //96為顯示器DPI var bitmapRender = new RenderTargetBitmap((int)element.ActualWidth, (int)element.ActualHeight + 100, 96, 96, PixelFormats.Pbgra32);//位圖 寬度 高度 水平DPI 垂直DPI 位圖的格式 高度+100保證整個圖都能截取 //控件內容渲染RenderTargetBitmap bitmapRender.Render(element); BitmapEncoder encoder = null; //選取編碼器 switch (type.ToUpper()) { case "BMP": encoder = new BmpBitmapEncoder(); break; case "GIF": encoder = new GifBitmapEncoder(); break; case "JPEG": encoder = new JpegBitmapEncoder(); break; case "PNG": encoder = new PngBitmapEncoder(); break; case "TIFF": encoder = new TiffBitmapEncoder(); break; default: break; } //對於一般的圖片,只有一幀,動態圖片是有多幀的。 encoder.Frames.Add(BitmapFrame.Create(bitmapRender));//添加圖 if (!Directory.Exists(System.IO.Path.GetDirectoryName(outputPath))) Directory.CreateDirectory(System.IO.Path.GetDirectoryName(outputPath)); using (var file = File.Create(outputPath))//存儲文件 encoder.Save(file); }
這里的一個關鍵問題在於如何循環生成多張圖片.
private void GenerateOneBitMap(IOTableForClip Clip, int StartAddr, int CurAddr, string[] Itxtss, string[] Qtxtss) { string[] Itxts = new string[16]; string[] Qtxts = new string[16]; for (int i = 0; i < 16; i++) { var Offset = (CurAddr - StartAddr) * 8; if (Itxtss.Length >= Offset + i + 1) { Itxts[i] = Itxtss[Offset + i]; } if (Qtxtss.Length >= Offset + i + 1) { Qtxts[i] = Qtxtss[Offset + i]; } } generateStrings(CurAddr, out string[] Iaddrs, out string[] Oaddrs); GenerateItems(Iaddrs, Oaddrs, Itxts, Qtxts); String[] PathName = ParentOutputFile.Split('.'); this.UpdateLayout(); GetPicFromControl(grid1, "PNG", PathName[0]+"_"+((CurAddr-StartAddr)/2)+".PNG"); }
UpdateLayout();函數非常的關建,只有刷新后,才能正確的顯示實際的圖片.