成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

如何進(jìn)行WPF控件編程

這篇文章給大家介紹如何進(jìn)行WPF控件編程,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

創(chuàng)新互聯(lián)專業(yè)提供香港機(jī)房服務(wù)器托管服務(wù),為用戶提供五星數(shù)據(jù)中心、電信、雙線接入解決方案,用戶可自行在線購(gòu)買香港機(jī)房服務(wù)器托管服務(wù),并享受7*24小時(shí)金牌售后服務(wù)。

WPF提供了一系列預(yù)定義組件以供UI開發(fā)人員使用。但軟件開發(fā)人員仍常常需要自行編寫滿足特定要求的控件。以Spinner控件為例,講解如何以派生方式完成自定義控件的編寫。

一.動(dòng)手前的思考

在著手開始編寫控件之前,我們需要思考Spinner需要以怎樣一種方式實(shí)現(xiàn)?MSDN建議使用三種控件實(shí)現(xiàn)方式:從UserControl類派生,從Control類派生以及從FrameworkElement類派生。

要正確地從這三種方式中作出選擇,軟件開發(fā)人員首先需要了解這些實(shí)現(xiàn)方法的特點(diǎn)。從UserControl類派生和WPF應(yīng)用程序開發(fā)模型非常類似:控件僅僅由現(xiàn)有控件組成,并通過XAML描述。其支持樣式和觸發(fā)器。通過這種方式定義的自定義控件并不希望軟件開發(fā)人員通過模板指定其外觀。

從Control類派生則是大多數(shù)控件開發(fā)所使用的方式。與從UserControl類派生這一方法不同,其外觀并不是由關(guān)聯(lián)的XAML文件指定的,而常常由主題文件所指定。從該類派生的特點(diǎn)有:1) 可以通過ControlTemplate自定義控件的外觀。2) 控件可以支持不同的主題。

而從FrameworkElement類派生則需要徹底拋棄使用控件元素組合的開發(fā)方式(無論是在Template中還是UserControl定義中)。生成基于FrameworkElement的組件有兩種標(biāo)準(zhǔn)方法:直接呈現(xiàn)和自定義元素組合。

直接呈現(xiàn)是指重寫FrameworkElement的OnRender方法,并提供顯式定義組件視覺效果的DrawingContext操作,如Image類和Border類就是通過這種方法定義的。例如精簡(jiǎn)后的Border類的OnRender()函數(shù)如下所示:

protectedoverridevoidOnRender(DrawingContext dc)  {  ……  dc.DrawRoundedRectangle(…);  ……  }

另一種則是使用Visual類實(shí)例組合對(duì)象外觀。如Track就是使用組合對(duì)象外觀的實(shí)例。Track類提供了Thumb屬性,并在其內(nèi)部實(shí)現(xiàn),如ArrangeOverride()函數(shù)中,都考慮了對(duì)該組成的使用。

從FrameworkElement中派生的優(yōu)點(diǎn)有:1) 可以完成對(duì)控件外觀的精確控制,而不僅僅是簡(jiǎn)單的元素組合。2) 通過定義自己的繪制邏輯定義控件的外觀。

很顯然,Spinner控件需要使用從Control類派生的方法,以提供對(duì)模板的支持。當(dāng)然,這里并非是指Spinner控件直接從Control類派生,而是選擇Control類的一個(gè)派生類作為Spinner的基類。這實(shí)際上與WPF中的控件類型組織特點(diǎn)有關(guān)。WPF中,代表各個(gè)控件的類型的繼承層次按照控件特征以非常細(xì)致的方式劃分,并在每個(gè)繼承層次中僅添加對(duì)一個(gè)到兩個(gè)特征的支持。就以Button類為例。該類型與Control類之間還存在著兩層派生:ContentControl類以及ButtonBase類。這兩個(gè)類型不僅僅分別提供了Content屬性以及命令相關(guān)的屬性,更重要的是,其內(nèi)部提供了支持這些屬性的默認(rèn)實(shí)現(xiàn)。在這種情況下,軟件開發(fā)人員僅僅在默認(rèn)實(shí)現(xiàn)不再滿足條件時(shí)才需要更改這些默認(rèn)實(shí)現(xiàn)所提供的邏輯,從而大大減少了開發(fā)新控件所需要的時(shí)間。

正是由于這個(gè)原因,我們需要在編寫一個(gè)控件之前仔細(xì)選擇其所需要使用的基類。選擇一個(gè)合適基類的標(biāo)準(zhǔn)就是該類型提供了最多的可重用功能,卻沒有提供過多的冗余功能。而基類的尋找也按照尋找相似控件,沿相似控件的繼承層次由高到低逐個(gè)篩選兩步。

在尋找相似控件的時(shí)候,軟件開發(fā)人員需要簡(jiǎn)單地揣摩一下該控件的使用方法,以尋找具有相似功能的控件。一般情況下,Spinner需要擁有一個(gè)***值,一個(gè)最小值,并擁有一個(gè)當(dāng)前值。軟件開發(fā)人員可以通過Spinner上的按鈕調(diào)整當(dāng)前值的大小,也可以通過輸入框直接輸入當(dāng)前值的大小。這和滾動(dòng)條控件非常相像,只不過滾動(dòng)條的直接輸入是通過Thumb完成的。然后我們需要反過來想想,是否ScrollBar提供了過多的Spinner所不需要或不支持的功能。顯然ScrollBar所提供的ViewportSize、Orientation等都不是Spinner所需要的屬性,因此其并不適合作為Spinner的基類。接下來我們可以依次考慮ScrollBar的各個(gè)基類,直到選中了一個(gè)較為適合的基類為止。就Spinner而言,RangeBase類就是一個(gè)較為合適的基類。

這里我們將遇到一個(gè)岔路口,那就是是否可以通過僅僅更改現(xiàn)有控件的模板這一方式滿足用戶的需求。如果可以,那么使用自定義模板則是更好的選擇。

在確定需要從某個(gè)類派生之后,軟件開發(fā)人員就應(yīng)該檢查該類所提供的各個(gè)依賴項(xiàng)屬性所具有的默認(rèn)值是否是一個(gè)合理的默認(rèn)值。例如RangeBase類指定了Value屬性的默認(rèn)值為0,最小值為0而***值為1。而對(duì)于Spinner而言,由于其常常需要操作整數(shù),因此這些默認(rèn)值都是不適合的。軟件開發(fā)人員需要在類型的靜態(tài)構(gòu)造函數(shù)中對(duì)這些默認(rèn)值進(jìn)行重寫:

RangeBase.ValueProperty.OverrideMetadata(typeof(Spinner), newFrameworkPropertyMetadata(10.0, OnValuePropertyChanged));

RangeBase.MaximumProperty.OverrideMetadata(typeof(Spinner), newFrameworkPropertyMetadata(20.0));

RangeBase.LargeChangeProperty.OverrideMetadata(typeof(Spinner), newFrameworkPropertyMetadata(1.0));

RangeBase.SmallChangeProperty.OverrideMetadata(typeof(Spinner), newFrameworkPropertyMetadata(1.0));

需要注意的是,OverrideMetadata()函數(shù)中所提供的屬性默認(rèn)值需要與屬性的類型匹配。例如在為Maximum屬性指定默認(rèn)值時(shí)使用整型數(shù)值20,那么對(duì)OverrideMetadata()函數(shù)的調(diào)用將導(dǎo)致程序崩潰。

在更改屬性的默認(rèn)值時(shí),軟件開發(fā)人員需要考慮控件所應(yīng)實(shí)際具有的意義。就以ComboBox和ListBox為例。在什么情況下應(yīng)使用ComboBox,而什么情況下應(yīng)使用ListBox呢?回答該問題的決定性因素就是這兩個(gè)控件所具有的特征,進(jìn)而導(dǎo)致的用戶體驗(yàn)的區(qū)別。ComboBox可以通過下拉列表顯示所有的可選項(xiàng),并通過編輯框組成顯示當(dāng)前項(xiàng)。這種對(duì)數(shù)據(jù)的顯示方式較ListBox占用了更小的空間,并突出顯示了當(dāng)前選中項(xiàng)。而相對(duì)于ComboBox,ListBox則在全面展示數(shù)據(jù),尤其是關(guān)聯(lián)型數(shù)據(jù)上較有優(yōu)勢(shì)。

同樣的,Spinner也有自己存在的意義。Spinner的中文名稱被稱為微調(diào)控件。從名稱上就可以看出,對(duì)Spinner的操作更多的是微小的調(diào)整。同時(shí),Spinner所提供的輸入框常常允許用戶直接輸入需要的數(shù)值,從而達(dá)到對(duì)數(shù)值精確的控制。也就是說,相對(duì)于ScrollBar等組成,其更注重于對(duì)值的精確指定。這也便是我在Spinner中添加精度控制屬性的一個(gè)原因。當(dāng)然,該部分內(nèi)容我會(huì)在后面繼續(xù)介紹。

在真正開始編寫控件之前,我們還需要考慮的事情就是用戶的使用方法。一般的用戶輸入都是通過鼠標(biāo)和鍵盤來完成的,因此我們就將用戶使用方法歸結(jié)為鼠標(biāo)和鍵盤兩類。

先來看看鼠標(biāo)。鼠標(biāo)需要考慮的主要分為擊鍵和滾輪兩種操作。在鼠標(biāo)左鍵點(diǎn)擊增加及減少按鈕時(shí),數(shù)值需要隨鼠標(biāo)的擊鍵而更改,并提供適當(dāng)?shù)耐庥^反饋。在鼠標(biāo)左鍵點(diǎn)擊輸入框時(shí),光標(biāo)需要移動(dòng)到相應(yīng)位置。而在鼠標(biāo)右鍵點(diǎn)擊輸入框時(shí),對(duì)文本進(jìn)行操作的菜單需要被彈出。在鼠標(biāo)滾輪滾動(dòng)時(shí),Value的值需要同時(shí)進(jìn)行更改。

接下來是鍵盤。一般情況下,鍵盤操作常常與非字符輸入鍵相關(guān)聯(lián)。例如用戶通過Tab等操作導(dǎo)航到控件之后,擁有輸入框組成的控件將自動(dòng)把其內(nèi)容全部選中。而用戶敲擊Enter鍵則表示他同意當(dāng)前數(shù)值。輸入焦點(diǎn)應(yīng)轉(zhuǎn)移到下一個(gè)控件以便用戶繼續(xù)操作。同時(shí)對(duì)于范圍類型控件而言,Up和Down表示小范圍數(shù)值變化,而PageUp和PageDown則表示大范圍數(shù)值變化。對(duì)于Spinner來說,微調(diào)是其主要功能,因此令小范圍數(shù)值變化和大范圍數(shù)值變化的值相等也是合情合理的。

***,在開始編寫控件之前,我們需要借鑒一下WPF中的基于同一基類的類似控件的實(shí)現(xiàn)。有關(guān)如何得到WPF源代碼的方式,請(qǐng)查看“從Dispatcher.PushFrame()說起”一文。通過觀察這些控件的實(shí)現(xiàn),我們可以更好地了解基類所提供的擴(kuò)展點(diǎn)以及這些擴(kuò)展點(diǎn)的使用方法。

二.開始實(shí)現(xiàn)

在本節(jié)中,我們就將開始著手實(shí)現(xiàn)Spinner。右鍵點(diǎn)擊項(xiàng)目文件,并在彈出菜單中選擇“Add”->“New Item”。在彈出的對(duì)話框中選擇“Custom Control(WPF)”并在名稱輸入框中輸入“Spinner.cs”,如圖所示:

如何進(jìn)行WPF控件編程

在點(diǎn)擊Add按鈕決定添加控件以后,Visual Studio將為我們添加兩個(gè)文件:Spinner.cs以及表示默認(rèn)主題的Generic.xaml。

2.1模板支持

通常情況下,我都會(huì)在主題文件中放置控件的一個(gè)簡(jiǎn)單模板實(shí)現(xiàn)。例如一開始,我在Generic.xaml中為Spinner定義了如下外觀:

<Style TargetType="{x:Type local:Spinner}"> <Setter Property="Control.Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:Spinner}"> <Border Background="{TemplateBinding Background}"BorderThickness="0.5"  BorderBrush="{TemplateBinding BorderBrush}"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition Width="20"/> </Grid.ColumnDefinitions> <TextBox x:Name="PART_Input"Grid.Column="0"Grid.Row="0" Grid.RowSpan="2"Margin="0.5"BorderThickness="0" Background="{TemplateBinding Background}"/> <RepeatButton x:Name="PART_Decrease"Grid.Column="1"Grid.Row="0" Margin="0.5"/> <RepeatButton x:Name="PART_Increase"Grid.Column="1"Grid.Row="1" Margin="0.5"/> </Grid> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>

雖然這并不是最終的控件外觀,但是通過該控件模板,我們可以隨時(shí)測(cè)試Spinner控件所包含的邏輯。

在該控件模板定義中,我們?yōu)閹讉€(gè)組成提供了特殊的名稱,如PART_Input。在模板定義中,以PART_開頭的名稱表示該名稱所對(duì)應(yīng)的組件是在控件內(nèi)部使用的模板定義中必不可少的一部分。為該控件所提供的其它模板同樣需要為這些名稱提供相應(yīng)的組成。

在自定義控件的實(shí)現(xiàn)中,軟件開發(fā)人員需要通過FrameworkTemplate.FindName()函數(shù)從當(dāng)前控件所使用模板的實(shí)例尋找具有特定名稱的組成。使用該函數(shù)的前提條件則是控件的模板已經(jīng)被施行。因此,調(diào)用該函數(shù)的最適合位置就是重載函數(shù)OnApplyTemplate()函數(shù)。如下面代碼所示:

publicoverridevoidOnApplyTemplate()  {  mInputTextBox = null;  mDecreaseButton = null;  mIncreaseButton = null;  base.OnApplyTemplate();  if(Template != null)  {  mInputTextBox = Template.FindName("PART_Input", this) asTextBox;  mDecreaseButton = Template.FindName("PART_Decrease", this) asRepeatButton;  mIncreaseButton = Template.FindName("PART_Increase", this) asRepeatButton;  }  }

為了能讓模板設(shè)計(jì)人員能夠知道這些必須在模板定義中出現(xiàn)的名稱以及這些名稱所對(duì)應(yīng)的控件類型,WPF提供了TemplatePart特性。該特性提供了兩個(gè)屬性Name及Type。Name用來標(biāo)記模板定義中需要添加的組成名稱,而Type則用來指明該名稱所需要具有的類型。如下面代碼所示:

[TemplatePart(Name="PART_Input", Type=typeof(TextBox)),  TemplatePart(Name="PART_Decrease", Type=typeof(RepeatButton)),  TemplatePart(Name="PART_Increase", Type=typeof(RepeatButton))]

在使用了該特性的情況下,模板設(shè)計(jì)人員可以直接通過該特性聲明得知模板中所應(yīng)具有的相應(yīng)元素以及其類型。

2.2功能實(shí)現(xiàn)

現(xiàn)在我們就需要考慮如何實(shí)現(xiàn)控件所對(duì)應(yīng)的功能。控件與模板之間進(jìn)行互動(dòng)的方法主要分為兩種:綁定和偵聽模板組成所發(fā)出的事件。在實(shí)現(xiàn)自定義控件時(shí),我們需要盡量使用綁定。但是對(duì)于特殊的處理邏輯,我們常常不能通過綁定完成相應(yīng)功能。在這種情況下,軟件開發(fā)人員就需要通過偵聽模板組成所發(fā)出的事件這一方式。

現(xiàn)在就來想想Spinner所需要使用的操作方式:對(duì)按鈕控件的輸入可能更改當(dāng)前值,同時(shí)在輸入框中執(zhí)行輸入并回車同樣可以確認(rèn)當(dāng)前值。對(duì)按鈕的點(diǎn)擊可以觸發(fā)Click事件,更可以觸發(fā)按鈕控件所關(guān)聯(lián)的命令,而在輸入框中敲擊回車鍵則只會(huì)觸發(fā)Keydown事件。因此在每次施行模板之后,我們需要為特定組成添加這些處理邏輯:

privatevoidAttach()  {  if(mDecreaseButton != null)  mDecreaseButton.Command = mDecreaseCommand;  &hellip;&hellip;  if(mInputTextBox != null)  {  mInputTextBox.Text = Value.ToString();  mInputTextBox.InputBindings.Add(newKeyBinding(mIncreaseCommand,  newKeyGesture(Key.Down)));  &hellip;&hellip;  mInputTextBox.PreviewKeyDown += PreviewTextBoxKeyDown;  mInputTextBox.LostKeyboardFocus += TextBoxLostKeyboardFocus;  }  }

與之對(duì)應(yīng)的是,在每次施行模板之前,我們則需要取消這些處理邏輯:

privatevoidDetach()  {  if(mInputTextBox != null)  {  mInputTextBox.PreviewKeyDown -= PreviewTextBoxKeyDown;  mInputTextBox.LostKeyboardFocus -= TextBoxLostKeyboardFocus;  }  }

這是因?yàn)樘砑拥南⑻幚砗瘮?shù)會(huì)對(duì)消息源生成一個(gè)引用。而取消該消息的偵聽則會(huì)釋放該引用。

這里,我們來看一下Attach()函數(shù)中所展示的互動(dòng)方式。

首先是命令。在這里我們使用mDecreaseCommand為按鈕指定命令。為什么使用命令,而不是路由事件?這取決于是否該用戶行為是否需要被用戶知曉。相對(duì)于路由事件,路由命令會(huì)在遇到相應(yīng)的執(zhí)行邏輯后不繼續(xù)執(zhí)行路由,從而對(duì)用戶不可見。

為了支持這些命令,軟件開發(fā)人員需要為Spinner設(shè)置CommandBinding以及InputBinding。CommandBinding為命令指定執(zhí)行邏輯,而InputBinding則為命令指定觸發(fā)命令的執(zhí)行條件。這部分邏輯通常在Spinner的構(gòu)造函數(shù)中完成:

publicSpinner()  {  CommandBindings.Add(newCommandBinding(mIncreaseCommand,  OnIncreaseCommand, CanExecuteIncreaseCommand));  &hellip;&hellip;  InputBindings.Add(newKeyBinding(mIncreaseCommand, newKeyGesture(Key.Down)));  &hellip;&hellip;  }

接下來要考慮的則是使用命令之外的另一種處理邏輯,事件。在事件PreviewKeyDown中,我們需要判斷用戶按下的是否是回車鍵。如果是,那么用戶的當(dāng)前輸入將會(huì)被驗(yàn)證,并根據(jù)用戶輸入的正確性決定對(duì)Value值的刷新。這里存在著幾個(gè)需要寫到的問題。首先就是為什么用Preview-事件。TextBox會(huì)在處理用戶輸入時(shí)將KeyDown事件的handled設(shè)置為true,因此軟件開發(fā)人員不能直接使用KeyDown事件,而是使用PreviewKeyDown事件。另一個(gè)則是InputBinding有效的時(shí)機(jī)。InputBinding是由KeyDown事件所驅(qū)動(dòng)的。在KeyDown事件被TextBox處理之前,TextBox實(shí)例內(nèi)設(shè)置的InputBinding將被處理;而在TextBox中,KeyDown事件的handled屬性會(huì)在處理過程中被設(shè)置為true,從而使TextBox的各個(gè)祖先元素失去了處理InputBinding的機(jī)會(huì)。這也便是Attach()函數(shù)為TextBox類型成員mInputTextBox添加額外的InputBinding的原因:

privatevoidAttach()  {  &hellip;&hellip;  if(mInputTextBox != null)  {  &hellip;&hellip;  mInputTextBox.InputBindings.Add(newKeyBinding(&hellip;&hellip;));  &hellip;&hellip;  }  }

接下來,考慮到微調(diào)控件的每次調(diào)整可能并不是整數(shù),因此我們還需要為Spinner提供一種控制顯示精度的方法。這便是添加Precision屬性的原因。該屬性會(huì)通過double.ToString()函數(shù)控制當(dāng)前值的格式化執(zhí)行方式,以顯示特定的精度:

privatestringGetValueString()  {  intprecision = Precision <0? 0: Precision;  stringformat = string.Format("F{0}", precision);  returnValue.ToString(format);  }

2.3更改主題

在實(shí)現(xiàn)了所有功能之后,我們就應(yīng)該開始準(zhǔn)備為控件指定主題。

首先要提及的就是如何為控件指定默認(rèn)樣式。為控件指定默認(rèn)樣式的方法主要是通過設(shè)置DefaultStyleKey屬性完成的。完成該工作的最常見方法就是在靜態(tài)構(gòu)造函數(shù)中重寫依賴項(xiàng)屬性DefaultStyleKey的默認(rèn)值:

FrameworkElement.DefaultStyleKeyProperty.OverrideMetadata(typeof(Spinner), newFrameworkPropertyMetadata(typeof(Spinner)));

接下來,我們就需要在默認(rèn)主題文件Generic.xaml中添加Spinner的外觀定義。該外觀定義的部分代碼如下:

<ControlTemplate x:Key="RepeatButtonTemplate"TargetType="{x:Type ButtonBase}"> <Border x:Name="Chrome"BorderThickness="0, 0, 1, 1"Background="Transparent"&hellip;> <ContentPresenter &hellip;/> </Border> &hellip;  </ControlTemplate> <Style TargetType="{x:Type local:Spinner}"> &hellip;  <ControlTemplate TargetType="{x:Type local:Spinner}"> <Border &hellip;> &hellip;  <TextBox x:Name="PART_Input"Grid.Column="0"Grid.Row="0"Grid.RowSpan="2" Background="{TemplateBinding Background}"&hellip;/> <RepeatButton x:Name="PART_Decrease"Grid.Column="1"Grid.Row="0" Template="{StaticResource RepeatButtonTemplate}"&hellip;> <Path x:Name="UpTriangle"StrokeThickness="1"Data="M 3,0 L 0,4 6,4 Z"&hellip;/> </RepeatButton> &hellip;  </Border> <ControlTemplate.Triggers> <DataTrigger Binding="{Binding IsMouseOver, ElementName=PART_Decrease}"&hellip;> <Setter TargetName="UpTriangle"Property="Stroke"Value="Blue"/> <Setter TargetName="UpTriangle"Property="Fill"Value="Blue"/> </DataTrigger> &hellip;  </ControlTemplate.Triggers> </ControlTemplate> &hellip;  </Style>

如果軟件開發(fā)人員希望為不同的Windows主題提供不同的外觀,那么他可以通過為特定主題提供特定外觀或是偵聽WM_THEMECHANGED事件完成。在需要為特定主題提供特定外觀時(shí),軟件開發(fā)人員需要在項(xiàng)目的Themes文件夾下添加對(duì)應(yīng)的主題文件,如Windows經(jīng)典主題對(duì)應(yīng)的就是ThemesClassic.xaml。而如果對(duì)主題更改的支持是通過偵聽WM_THEMECHANGED事件完成的,那么對(duì)控件模板的更換則需要通過代碼顯式完成。

同時(shí),軟件開發(fā)人員還可以通過ThemeInfo特性指定主題文件所存在的位置。該特性擁有兩個(gè)和主題相關(guān)的屬性:GenericDictionaryLocation以及ThemeDictionaryLocation。這兩個(gè)屬性分別指定了與主題相關(guān)的通用資源所在的位置以及特定于主題的資源所在的位置。它們都接受類型為ResourceDictionaryLocation的枚舉值。該枚舉值中,None表示不使用主題,SourceAssembly表示主題存在于當(dāng)前程序集中,而ExternalAssembly則表示主題字典存在于受主題影響的外部程序集中。

對(duì)于本例的示例控件Spinner而言,對(duì)ThemeInfo主題的使用如下:

[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]

關(guān)于如何進(jìn)行WPF控件編程就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

當(dāng)前標(biāo)題:如何進(jìn)行WPF控件編程
URL地址:http://jinyejixie.com/article40/gpedeo.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、全網(wǎng)營(yíng)銷推廣、網(wǎng)站制作、微信公眾號(hào)、外貿(mào)網(wǎng)站建設(shè)、網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

小程序開發(fā)
霍林郭勒市| 乌兰浩特市| 盐亭县| 大兴区| 禄劝| 突泉县| 罗城| 六盘水市| 六盘水市| 嵊泗县| 和林格尔县| 大名县| 临高县| 嘉祥县| 昔阳县| 防城港市| 馆陶县| 龙门县| 怀仁县| 双鸭山市| 安新县| 郸城县| 五华县| 封开县| 武鸣县| 新丰县| 渑池县| 汉寿县| 东山县| 七台河市| 郎溪县| 武义县| 闻喜县| 介休市| 白河县| 桂阳县| 霍州市| 和田县| 泌阳县| 巴彦淖尔市| 浮山县|