【WPF】根據選項值顯示不同的編輯控制項(使用DataTemplateSelector)

来源:https://www.cnblogs.com/tcjiaan/p/18274217
-Advertisement-
Play Games

接了一個小雜毛項目,大概情形是這樣的:ZWT先生開的店是賣拆片機的,Z先生不僅賣機器,還貼心地提供一項服務:可以根據顧客需要修改兩個電機的轉向和轉速(機器廠家有給SDK的,但Z自己不會寫程式)。廠家有配套一個調節器,調整參數時連接到拆片機的串口上,然後旋轉按鈕可以調速,撥碼開關可以設定電機正轉還是反 ...


接了一個小雜毛項目,大概情形是這樣的:ZWT先生開的店是賣拆片機的,Z先生不僅賣機器,還貼心地提供一項服務:可以根據顧客需要修改兩個電機的轉向和轉速(機器廠家有給SDK的,但Z自己不會寫程式)。廠家有配套一個調節器,調整參數時連接到拆片機的串口上,然後旋轉按鈕可以調速,撥碼開關可以設定電機正轉還是反轉。但Z覺得調速器長得像個馬桶水箱似的,既不好看也不便攜帶。最慘的是,有的客戶的車間里有很多台機子,一臺一臺的連上串口調很費勁。於是,學過電工的Z不知道哪搞了一個串口 Hub,電腦向 Hub 發數據,然後會將數據群發給所有連接的拆片機(這種Hub忘了叫什麼,十幾年前老周曾在某工廠里見過)。

Z先生經過 N 個中間人介紹,找到老周。去Z的店參觀了一下,瞭解情況後。當時老周可能被蚊子叮昏了頭腦,覺得這不簡單嗎,便答應Z老闆說三天可以完成。不過,這做起來確實不複雜,基礎代碼當天晚上用了兩個多小時就搞定了。串口發數據這事也不難,就一個 SerialPort 類就搞定的。協議方面有文檔,一個位元組數組,代入參數就行。每台拆片機的編組號和參數就用一人 Sqlite 資料庫存放就完事,配上 EF Core 讀寫起來那簡直是入門級的難度。

不過,在第一次實測時遇到一個問題,每台機器的參數是不同的,需要提供一個界面,讓用戶可以增/刪/改/查。也就是說,機器參數不能用類來封裝,靜態語言又不能動態生成屬性。當然,這也不是靜態語言的錯,而是思路的問題。我幹嗎非要用類封裝呢?不封裝不就更可以動態變化了嗎?然後,老周的生理 CPU 飛速運轉,眼前浮現著一張張熟悉的面孔—— 用字典來存儲,用JSON來做,用XML來做……避免產生過多不必要的數據文件,老周決定在資料庫加個表,一個選項(參數)就是一條數據記錄。欄位如選項名稱,選項值,備註,添加時間,最後修改時間,添加人(誰操作的)等。

隨後,又有問題了,雖然在資料庫中我把選項(參數)值統一用文本來表示,但在用戶界面上,總不能全放個 TextBox 在那裡。最重要的是全用 TextBox 的話,驗證的代碼就要寫很多了。比如:你輸入的是日期嗎?你輸入的是整數嗎?Bool 值的東西為什麼還要讓人填呢,直接CheckBox不好嗎?現在的情況是,機器參數是用戶動態添加的,在佈局界面時我不能寫死了,畢竟事先不能確定用下拉列表還是文本框。那就在運行時動態呈現控制項吧。

思路有了,但方法有很多種。老周當初吹下了三天完工的牛皮,所以,創建新控制項的思路可能更花時間,而且感覺也沒必要。經過短暫的計劃,老周的想法:

1、在編輯界面上用一個 ListBox 或 ItemsControl,這樣可以免去許多排版的功夫。就把界面當成一個列表框控制項,每個被編輯的選項就是一個列表項。這樣添加和刪除也方便,直接 Binding 就好了。

2、為 ItemsControl 定義列表項模板,是 DataTemplate,不是控制項模板。重做控制項模板跟創作新控制項一樣,費時間。

3、DataTemplate 中,一個 TextBlock 用來顯示項目標簽(不可修改的文本),然後值呢?

4、選項值用的控制項是不固定的,事先不可知,老周就放了個 ContentControl 在那裡,就當作占位符用吧。為什麼選它呢?因為它有 Content 屬性,可以放任何東西,而且配套一個 ContentTemplate 屬性,類型正是 DataTemplate。當然,也可以用類似功能的控制項。

老周的想法就是,用 ContentTemplate 去引用其他的數據模板,當然數據模板也不能寫死的。即要定義多個模板,裡面放不同的控制項,需要根據選項的需求選擇不同的模板。於是,有一個類可以用上——DataTemplateSelector。

這個類有一個虛方法叫 SelectTemplate,能夠根據相同數據運態返回數據模板。派生類只要重寫這個方法就 OK。

哦,補充一下,什麼是拆片機。大伙伴看看你身上穿的戰袍,是不是由一塊塊布組成?服裝廠先要依據圖紙,用選定的線織成一塊塊的“散片”,針織機有橫機和縱機,橫向的多見。以前是手動操作,現在是電腦控制。這些“散片”可交給縫盤工人(做縫盤的一般是女孩,以前那些手動織機的話,男女都有)“組裝”起來。下圖是常見的縫盤機(不是縫紉機,二者不同的),老周家裡還有三台,23年前的型號,有兩台還是全新未拆封的。

縫盤機的套口編號要與針織機的匹配使用,如果混用會出問題的,以前內銷毛衣常見的12針(這個簡單說就是代表線眼的疏密)一般會配用14號的縫盤機(當然也不是絕對,看工人經驗,有時候接近編號的也行)。

從上世紀 90 年代起,老周家裡做過十幾年的毛衣加工,現在是租給別人做,近20臺電腦橫機,全天24小時工作。所以,服裝方面的東西,老周還是懂一點點的。

拆片就是把編織好的衣片拆解,變回一筒一筒的毛線。這個原因就很複雜,可能針織時效果不好,可能拿錯圖紙了,可能多出來不用的……把衣片重新變回毛線,還可以重覆用,不浪費。有些片可能被機器里的潤滑油污染,以前有的師傅懶得處理,是直接不要的(在農村,以前有些人家用來生火煮水煮飯),不過,這些毛料燃燒起來是環境污染,而且味道讓人很難受。現在很少有人這麼乾。畢竟,別說燃氣竈,連電磁爐在農村都很普及了。直接生火做飯的很少,就算要生火,也會選一些天然的柴木。

拆片機結構很簡單,用夾子固定衣片,先人工抽出部分絲線,在橡皮輪上纏繞二三十圈,然後開動電機就行,工作起來有點像打毛機。拆片機現在並沒有標準的產品,都是個別有“本事”的工廠或者像Z先生這樣的,自己做的。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

不得了,這次扯的廢話太長了。不能再說了,不然就真變成水文了。下麵說重點。

步驟一:定義選項包裝類

哦,老周上面只是說,不用類來封裝選項數據,但每條選項記錄還是要封裝的。這個類與資料庫中的選項表映射。為了簡單,老周刪除了像備註、操作員、時間、父級選項等無關的屬性。

public class OptionItem : INotifyPropertyChanged
{
    private string _labelName;
    private string _objValue;
    private bool _required;
    private UIElementType _uiType;
    private string _listItems;

    public int Id { get; set; }

    /// <summary>
    /// 選項的標簽文本
    /// </summary>
    public string LabelName
    {
        get => _labelName;
        set
        {
            _labelName = value;
            OnMyPropertyChanged();
        }
    }

    /// <summary>
    /// 選項的值
    /// </summary>
    public string ObjectValue
    {
        get { return _objValue; }
        set
        {
            _objValue = value;
            OnMyPropertyChanged();
        }
    }

    /// <summary>
    /// 是否為必填
    /// </summary>
    public bool Required
    {
        get => _required;
        set
        {
            _required = value;
            OnMyPropertyChanged();
        }
    }

    /// <summary>
    /// 界面控制項類型
    /// </summary>
    public UIElementType UIType
    {
        get => _uiType;
        set
        {
            _uiType = value;
            OnMyPropertyChanged();
        }
    }

    /// <summary>
    /// 顯示在下拉列表中的內容,只有需要下拉列表控制項時才用到
    /// </summary>
    public string ListItems
    {
        get => _listItems;
        set
        {
            _listItems = value;
            OnMyPropertyChanged();
        }
    }

    // 屬性更改後調用的方法
    private void OnMyPropertyChanged([CallerMemberName] string propertyName = null)
    {
        if (string.IsNullOrEmpty(propertyName))
        {
            return;
        }
        PropertyChanged?.Invoke(this, new(propertyName));
    }

    // 屬性更改通知事件
    public event PropertyChangedEventHandler PropertyChanged;
}

實現 INotifyPropertyChanged 介面,這個不用多介紹,可以讓綁定對象能實時獲得更改通知。UIElementType 是一個枚舉類型,表示要呈現什麼樣的控制項,映射到資料庫表時用整型存儲。

public enum UIElementType
{
    TextBox,                // 普通文本框
    NumberBoxInt,           // 文本框,但輸入整數用
    NumberBoxFloat,         // 文本框,但輸入浮點數用
    CheckBox,               // 覆選框
    DropdownList,           // 下拉列表框
    DatePicker              // 日期選擇器
}

這老周項目所需要用的控制項,你可以根據實際添加成員。WPF的編輯控制項其實很少,比 WinForms 還少。大概開發團隊覺得 WPF 的控制項可以自己設計。雖然控制項模板方便換,但做起來也要一定工作量的,反正嘛,非必要不創作控制項。有大伙伴會問:怎麼不用開源的第三方控制項?如果老周自己用的話,倒是可以的。可是項目一旦是商用的就……有些開源協議是很讓人無語的,而且不少項目在商用上要授權的。比如那個 Qt,到處告人,“一不小心就會一團糟”。所以咱們這邊做上位機、客戶端什麼的基本用 .NET,Qt 幾乎沒幾個人用,很多公司都不敢用,怕被找上門。

購買授權這種事不要問老周,因為老周也搞不懂。但老周大膽懷疑,這些王X蛋是不是故意的,想通過打官司來發家致富?其實大伙常用的手機 App 錶面免費,實際上你如果有心情去閱讀一下用戶協議,你會冒出一身冷汗。處處給你設坑,你想不跳都不行。比如微信輸入法什麼的,你如果用它來寫小說,那小心,可能你的版權會變成別人的。還有一些不要臉的套殼 AI 服務,在你我不知情的前提下,把咱們的東西變成它們的數據、模型的一部分,產權歸誰?科學技術很強大,可有沒有質疑過,現在搞科技創新的初心是什麼?為了割矮腳白菜?還是為戰爭服務?還是……

不過,老周從 2010 年至今,接的很多項目 90% 是用 .NET 做的,至少目前沒出什麼授權上的問題,也沒收到過“綠屍寒”。微軟還是靠譜的。

為了在資料庫中能夠相容,表示選項值的 ObjectValue 屬性定義為字元串類型,在資料庫也是用文本存放。因為選項值可能是浮點數和日期,但文本很強大,啥值它都能表示。LabelName 屬性是顯示在用戶界面上的,不能被編輯。Required 屬性表示此欄位值是不是必需的,如果是,會在標簽旁邊顯示一個星號(*)。在綁定時,用 BoolValueConverter 轉換器把 bool 值轉為控制項的 Visibility 屬性值,這個轉換器是 .NET 類庫自有的,不用我們自己寫。

 ListItems 屬性只有當控制項是 ComboBox 時才用到,字元串內容,每個項用逗號分隔,顯示在下拉列表中。由於它的值是一個字元串,而 ComboBox 的 item source 是列表類型。需要寫一個轉換器,把字元串分割成一個數組。

public class StrToListConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string list = (string)value;
        var splis = list.Split(',').Select(s => s.Trim());
        return splis.ToArray();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return null;
    }
}

因為綁定是單向的,所以 ConvertBack 不需要,返回 null 了事。

同樣的道理,如果用的是 CheckBox 控制項,需要 bool 和 string 之間的轉換。

public class BoolValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // 由於數據的值都用字元串表示,所以這裡可以直接轉成字元串
        string strValue = (string)value;
        if (bool.TryParse(strValue, out bool val))
        {
            return val;
        }
        return false;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        // 轉回去的時候,都是字元串
        return value.ToString();
    }
}

由於 CheckBox 控制項用於編輯,需要雙向綁定,所以 ConvertBack 方法要實現。

步驟二:從TextBox派生一個類

儘管原則上不創作新控制項,但由於 TextBox 除輸入任意文本外,有些選項只能讓用戶輸入數字。有大伙伴會說,用 InputScope 不就行了嗎。還真的不行。input scope 只對觸控屏幕鍵盤有效,對於實體鍵盤無法限制。因此,為了方便,只好派生出一個類,然後重寫 OnPreviewTextInput 方法,把非數字字元排除。其實,處理 TextBox 的 PreviewTextInput 事件也可以的,不過派生一個類來處理,完整性更好,也方便直接把控制項代碼複製到別的項目使用。Preview開頭的事件是【隧道】事件,即從 XAML 樹的外層向內引發,而 TextInput 事件是冒泡式的,從內向外引發。鍵盤事件由 WM_KEY*** 相關消息產生,操作系統會將其傳遞給視窗。視窗處理了再到裡面的控制項處理。我們應該處理 Preview 事件,當輸入的字元不是數字時,不讓它向下傳播,這樣 TextBox 就不會接收這個字元了。

public class NumberTextBox : TextBox
{
    public NumberTextBox()
    {
        TextNumberType = NumberType.Integer;
        // 禁用輸入法
        InputMethod.SetIsInputMethodEnabled(this, false);
        // 設置輸入範圍是數字,這隻對觸控鍵盤有效
        InputScope ipscope = new();
        ipscope.Names.Add(new InputScopeName(InputScopeNameValue.Number));
        this.InputScope = ipscope;
    }

    /// <summary>
    /// 獲取/設置數字類型
    /// </summary>
    public NumberType TextNumberType { get; set; }

    protected override void OnPreviewTextInput(TextCompositionEventArgs e)
    {
        Debug.WriteLine($"Text={e.Text}, ControlText={e.ControlText}");
        string pattern = TextNumberType == NumberType.Integer ? "[0-9]+" : "[0-9.]+";
        var rgxres = Regex.Match(e.Text, pattern);
        if (!rgxres.Success)
        {
            e.Handled = true;
            return;
        }
        base.OnPreviewTextInput(e);
    }
}

public enum NumberType
{
    /// <summary>
    /// 整數
    /// </summary>
    Integer,
    /// <summary>
    /// 浮點數
    /// </summary>
    Floating
}

老周這裡就用正則表達式來匹配,實際上它只匹配一個字元。只要有字元輸入,TextInput 相關的事件就會引發,而關聯的文本只是剛剛輸入的那個字元,而不是 TextBox 中的所有字元。也就是說,這段代碼只是限制,並不能驗證輸入的對不對。其實這代碼還不嚴謹,如果用戶已經輸入過小數點了,也可能出現輸入 N 個小數點的問題。我們也可以優化一下。

protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
    ……
    if (TextNumberType == NumberType.Floating && e.Text == ".")
    {
        // 看看他輸入了幾個小數點
        TextBox tb = (TextBox)e.OriginalSource;
        int index = tb.Text.LastIndexOf('.');
        // 索引如果有效,說明之前輸入過小數點
        if(index > -1)
        {
            e.Handled= true;
            return;
        }
    }

    base.OnPreviewTextInput(e);
}

e.Handled = true 會阻止事件向下傳播,就能達到阻止輸入某些字元的目的。註意,TextInput 相關事件引發時,一般我們只能獲取剛輸入的字元,要結合 TextBox 現有的文本來分析,就必須從 TextBox.Text 屬性獲取。上面代碼是先判斷當前輸入的是浮點數,且當前輸入的字元是小數點,如果是,就從 TextBox 現有文本中查找小數點。如果找到,說明之前輸入過,就不能再輸入了。

當然了,限制並不能代替數驗證功能。如果需要,還要進一步用代碼檢查輸入的值是否有效。

步驟三:準備一堆數據模板

要用模板選擇器篩選要用的數據模板,首先得準備一系列待選模板,放進資源字典中。

<Window.Resources>
    <!--轉換器-->
    <BooleanToVisibilityConverter x:Key="btvCvt" />
    <local:BoolValueConverter x:Key="boolValCvt"/>
    <local:StrToListConverter x:Key="strToListCvt"/>
    <!--下麵幾個模板是用於選擇的-->
    <DataTemplate x:Key="selTempAnyTxt">
        <TextBox Text="{Binding Path=ObjectValue, Mode=TwoWay}" InputMethod.IsInputMethodEnabled="False"/>
    </DataTemplate>
    
    <DataTemplate x:Key="selTempNumberInt">
        <local:NumberTextBox Text="{Binding Mode=TwoWay, Path=ObjectValue}" TextNumberType="Integer"/>
    </DataTemplate>
    
    <DataTemplate x:Key="selTempNumberFloat">
        <local:NumberTextBox Text="{Binding Mode=TwoWay, Path=ObjectValue}" TextNumberType="Floating"/>
    </DataTemplate>

    <DataTemplate x:Key="selTempList">
        <ComboBox ItemsSource="{Binding Path=ListItems, Converter={StaticResource strToListCvt}}"
                  Text="{Binding Path=ObjectValue, Mode=TwoWay}"
                  IsReadOnly="True"
                  IsEditable="True"/>
    </DataTemplate>
    
    <DataTemplate x:Key="selTempCheckBox">
        <CheckBox IsChecked="{Binding Path=ObjectValue,Mode=TwoWay,Converter={StaticResource boolValCvt}}" Content=""/>
    </DataTemplate>
    
    <DataTemplate x:Key="selTempDatePicker">
        <DatePicker Text="{Binding Path=ObjectValue, Mode=TwoWay}" SelectedDateFormat="Short"/>
    </DataTemplate>
    <!--END-->
……
</Window.Resources>

要定義一堆模板,看著複雜,其實很簡單。

步驟四:編寫模板選擇器

    public class MyTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            OptionItem opt = (OptionItem)item;

            switch (opt.UIType)
            {
                case UIElementType.TextBox:
                    return TextTemplate;
                case UIElementType.NumberBoxInt:
                    return IntNumTemplate;
                case UIElementType.NumberBoxFloat:
                    return FloatNumTemplate;
                case UIElementType.DropdownList:
                    return DropListTemplate;
                case UIElementType.CheckBox:
                    return CheckBoxTemplate;
                case UIElementType.DatePicker:
                    return DatePickerTemplate;
            }
            return TextTemplate;
        }

        #region 公共屬性,用於引用模板
        public DataTemplate TextTemplate { get; set; }
        public DataTemplate IntNumTemplate { get; set; }
        public DataTemplate FloatNumTemplate { get; set; }
        public DataTemplate CheckBoxTemplate { get; set#endregion
    }

註意那六大屬性,待會把選擇器放到XAML資源中,可以引用我們前面定義的那些模板。重寫 SelectTemplate 方法是核心。根據 UIType 屬性來判斷要用的模板。這是咱們前面定義選項類時用的枚舉。

步驟五:套用

        <!--模板選擇器-->
        <local:MyTemplateSelector x:Key="tempSelt"
                                  TextTemplate="{StaticResource selTempAnyTxt}"
                                  IntNumTemplate="{StaticResource selTempNumberInt}"
                                  FloatNumTemplate="{StaticResource selTempNumberFloat}"
                                  DropListTemplate="{StaticResource selTempList}"
                                  CheckBoxTemplate="{StaticResource selTempCheckBox}"
                                  DatePickerTemplate="{StaticResource selTempDatePicker}"
                               />
        <DataTemplate x:Key="itemTemp" DataType="local:OptionItem">
            <Grid MinWidth="300" HorizontalAlignment="Center" Margin="0,5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <StackPanel Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,5,0">
                    <TextBlock>
                        <Run Text="{Binding Mode=TwoWay, Path=LabelName}"/>
                        <Run Text=":"/>
                    </TextBlock>
                    <TextBlock Visibility="{Binding Required, Converter={StaticResource btvCvt}}" Foreground="Red" FontSize="16">*</TextBlock>
                </StackPanel>
                <ContentControl Grid.Column="1" Content="{Binding}" ContentTemplateSelector="{StaticResource tempSelt}"/>
            </Grid>
        </DataTemplate>

Key 為 itemTemp 的模板是給 ItemsControl 控制項用的。而前面的那些模板只是給 ContentControl 用的。也就是說,ItemsControl 控制項的列表項套用 itemTemp 模板,而 itemTemp 模板裡面的 ContentControl 控制項又套用前面定義的模板(由模板選擇器決定)。

註意,綁定 Required 屬性的 TextBlock 只有一個“*”字元,當 Required 屬性為true時,該控制項顯示星號,表示此欄位必填。

步驟六:界面佈局

在界面上放一個 ItemsControl 控制項就能顯示各個選項欄位信息了。

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ScrollViewer Grid.Row="0">
        <ItemsControl x:Name="itOpt"
             ItemTemplate="{StaticResource itemTemp}"
             />
    </ScrollViewer>
    <TextBox Grid.Row="1" x:Name="txtDisp"/>
</Grid>

txtDisp 用來每 3 秒顯示一次選項類列表,這個只是用來觀察綁定有沒有雙向更新,並無實際用途。

下麵是代碼部分。

    public partial class MainWindow : Window
    {
        private ObservableCollection<OptionItem> _optionItems = new();
        private DispatcherTimer timer = new DispatcherTimer();
        public MainWindow()
        {
            InitializeComponent();
            timer.Interval = TimeSpan.FromSeconds(3);
            timer.Tick += OnTimer;
            _optionItems.Add(new()
            {
                Id = 1,
                Required = true,
                LabelName = "banch",
                UIType = UIElementType.NumberBoxInt,
                ObjectValue = "3"
            });
            _optionItems.Add(new()
            {
                Id = 2,
                LabelName = "factor",
                UIType = UIElementType.NumberBoxFloat,
                ObjectValue = "0.1",
                Required = false
            });
            _optionItems.Add(new()
            {
                Id = 3,
                LabelName = "tcc1",
                ObjectValue = "s-i-2",
                Required = true,
                UIType = UIElementType.TextBox
            });
            _optionItems.Add(new()
            {
                Id = 4,
                LabelName = "tcc2",
                ObjectValue = "k9",
                Required = true,
                UIType = UIElementType.TextBox
            });
            _optionItems.Add(new()
            {
                Id = 5,
                LabelName = "wt_mode",
                ObjectValue = "nor",
                Required = false,
                UIType = UIElementType.DropdownList,// 下拉列表
                ListItems = "cls,nor,slw,wdw"
            });
            _optionItems.Add(new()
            {
                Id = 6,
                LabelName = "flash",
                UIType = UIElementType.CheckBox,
                ObjectValue = "True",
                Required = false
            });
            _optionItems.Add(new()
            {
                Id = 7,
                LabelName = "size",
                ObjectValue = "8",
                UIType = UIElementType.NumberBoxInt,
                Required = false
            });
            _optionItems.Add(new()
            {
                Id = 8,
                LabelName = "reg_0xD3",
                Required = false,
                ObjectValue = "29",
                UIType = UIElementType.NumberBoxInt
            });
            // 綁定
            itOpt.ItemsSource = _optionItems;
            timer.Start();
        }

        private void OnTimer(object sender, EventArgs e)
        {
            StringBuilder bd = new StringBuilder();
            foreach (OptionItem oi in _optionItems)
            {
                bd.AppendLine($"{oi.LabelName} = {oi.ObjectValue}");
            }
            txtDisp.Text = bd.ToString();
        }
    }

DispatchTimer 只是用來周期性更新顯示,沒其他用途。

 

運行程式後,你會發現。程式會根據各個選項的設

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、概述 Spring MVC是Spring Framework的Web開發部分,是基於Java實現MVC的輕量級Web框架。 官方文檔:https://docs.spring.io/spring-framework/docs/4.3.24.RELEASE/spring-framework-refe ...
  • 後兩次PTA總結 首先來看看第七次: 第七次相比於之前,添加了互斥開關元器件而且引入了並聯互串等等接法,按照我之前的設計,作出改動不算太難,我之前的遞歸已經可以按照順序儲存所以的元器件到一起去了,主要還是歸功於將串並聯電路繼承自元器件的方式十分有效,這樣就能夠將串並聯電路當作元器件一起處理,再按照遞 ...
  • 1. Spring MVC 獲取三個域(request請求域,session 會話域,application 應用域)對象的方式 @目錄1. Spring MVC 獲取三個域(request請求域,session 會話域,application 應用域)對象的方式2. Servlet中的三個域對象3 ...
  • ​《FFmpeg開發實戰:從零基礎到短視頻上線》一書的“10.2 FFmpeg推流和拉流”提到直播行業存在RTSP和RTMP兩種常見的流媒體協議。除此以外,還有比較兩種比較新的流媒體協議,分別是SRT和RIST。 其中SRT全稱為Secure Reliable Transport,中文叫做安全可靠傳 ...
  • 主題介紹 BeaconNav是基於typecho開發的一款導航主題,Beacon是燈塔的意思,希望使用者在知識的海洋里能夠如同有燈塔指引一樣目標明確,永遠不會迷失方向。 演示站點:https://nav.ilaozhu.com 主題特點 響應式設計,適配手機、平板、電腦等設備; 支持自定義 LOGO ...
  • 簡介: Redis是一款開源的使用ANSI C語言編寫、遵守BSD協議、支持網路、可基於記憶體也可持久化的日誌型、Key-Value高性能資料庫。Redis與其他Key-Value緩存產品相比有以下三個特點: 支持數據持久化,可以將記憶體中的數據保存在磁碟中,重啟可再次載入使用 支持簡單的Key-Val ...
  • 一:背景 1. 講故事 早就聽說過有什麼 網路邊緣計算,這次還真給遇到了,有點意思,問了下 chatgpt 這是幹嘛的 ? 網路邊緣計算是一種計算模型,它將計算能力和數據存儲位置從傳統的集中式數據中心向網路邊緣的用戶設備、感測器和其他物聯網設備移動。這種模型的目的是在接近數據生成源頭的地方提供更快速 ...
  • 之前分享中台 Admin.Core 的模塊代碼生成器,陸續也結合群友們的反饋,完善了一些功能和模板上的優化,而本篇將基於此代碼生成器生成一個通用代碼生成器模塊的基本代碼 後續再在此代碼的基礎上進行完善,製作一個通用的代碼生成器 ...
一周排行
    -Advertisement-
    Play Games
  • 問題 有很多應用程式在驗證JSON數據的時候用到了JSON Schema。 在微服務架構下,有時候各個微服務由於各種歷史原因,它們所生成的數據對JSON Object屬性名的大小寫規則可能並不統一,它們需要消費的JSON數據的屬性名可能需要大小寫無關。 遺憾的是,目前的JSON Schema沒有這方 ...
  • 首先下載centos07鏡像,建議使用阿裡雲推薦的地址: https://mirrors.aliyun.com/centos/7.9.2009/isos/x86_64/?spm=a2c6h.25603864.0.0.59b5f5ad5Nfr0X 其實這裡就已經出現第一個坑了 centos 07 /u ...
  • 相信很多.NETer看了標題,都會忍不住好奇,點進來看看,並且順便準備要噴作者! 這裡,首先要申明一下,作者本人也非常喜歡Linq,也在各個項目中常用Linq。 我愛Linq,Linq優雅萬歲!!!(PS:順便吐槽一下,隔壁Java從8.0版本推出的Streams API,抄了個四不像,一點都不優雅 ...
  • 在人生的重要時刻,我站在了畢業的門檻上,望著前方的道路,心中涌動著對未來的無限憧憬與些許忐忑。面前,兩條道路蜿蜒伸展:一是繼續在職場中尋求穩定,一是勇敢地走出一條屬於自己的創新之路。儘管面臨年齡和現實的挑戰,我仍舊選擇勇往直前,用技術這把鑰匙,開啟新的人生篇章。 迴首過去,我深知時間寶貴,精力有限。 ...
  • 單元測試 前言 時隔多個月,終於抽空學習了點新知識,那麼這次來記錄一下C#怎麼進行單元測試,單元測試是做什麼的。 我相信大部分剛畢業的都很疑惑單元測試是乾什麼的?在小廠實習了6個月後,我發現每天除了寫CRUD就是寫CRUD,幾乎用不到單元測試。寫完一個功能直接上手去測,當然這隻是我個人感受,僅供參考 ...
  • 一:背景 1. 講故事 最近在分析dump時,發現有程式的卡死和WeakReference有關,在以前只知道怎麼用,但不清楚底層邏輯走向是什麼樣的,藉著這個dump的契機來簡單研究下。 二:弱引用的玩法 1. 一些基礎概念 用過WeakReference的朋友都知道這裡面又可以分為弱短和弱長兩個概念 ...
  • 最近想把ET打表工具的報錯提示直接調用win系統彈窗,好讓策劃明顯的知道表格哪裡填錯數據,彈窗需要調用System.Windows.Forms庫。操作如下: 需要在 .csproj 文件中添加: <UseWindowsForms>true</UseWindowsForms> 須將目標平臺設置為 Wi ...
  • 從C#3開始,拓展方法這一特性就得到了廣泛的應用。 此功能允許你能夠使用實例方法的語法調用某個靜態方法,以下是一個獲取/創建文件的靜態方法: public static async Task<StorageFile> GetOrCreateFileAsync(this StorageFolder f ...
  • 在Windows 11下,使用WinUI2.6以上版本的ListView長這樣: 然而到了Win10上,儘管其他控制項的樣式沒有改變,但ListViewItem變成了預設樣式(初代Fluent) 最重大的問題是,Win10上的HorizontalAlignment未被設置成Stretch,可能造成嚴重 ...
  • 前言 周六在公司加班,幹完活後越顯無聊,想著下載RabbiitMQ做個小項目玩玩。然而這一下就下載了2個小時,真讓人頭痛。 簡單的講一下如何安裝吧,網上教程和踩坑文章還是很多的,我講我感覺有用的文章放在本文末尾。 安裝地址 erlang 下載 - Erlang/OTP https://www.erl ...