iT邦幫忙

2025 iThome 鐵人賽

DAY 22
0
Software Development

30天快速上手製作WPF選股工具 — 從C#基礎到LiteDB與Web API整合系列 第 22

Day 22 - 搜尋與篩選功能(結合 LiteDB 與 KD 黃金交叉)

  • 分享至 

  • xImage
  •  

昨天我們已經把下載的股票清單存進 LiteDB,並且可以從本地載入。
今天要進一步加上 搜尋與篩選功能,讓使用者可以:

  • 輸入關鍵字搜尋代號或名稱
  • 勾選「只顯示 KD 黃金交叉」來過濾股票

1) ViewModel:從 Repository 載入後進行篩選

我們將 IStockRepositoryIKdSignalService 結合,先把股票載入 ObservableCollection,再利用 ICollectionView 做即時篩選。

using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;
using System.Threading.Tasks;

public class StockFilterViewModel : INotifyPropertyChanged
{
    private readonly IStockRepository _repo;
    private readonly IKdSignalService _kdService;

    public ObservableCollection<StockProfile> Stocks { get; } = new();
    public ICollectionView View { get; }

    private string _query;
    public string Query
    {
        get => _query;
        set { _query = value; OnPropertyChanged(nameof(Query)); View.Refresh(); }
    }

    private bool _onlyGoldenCross;
    public bool OnlyGoldenCross
    {
        get => _onlyGoldenCross;
        set { _onlyGoldenCross = value; OnPropertyChanged(nameof(OnlyGoldenCross)); View.Refresh(); }
    }

    public ICommand LoadFromLocalCommand { get; }
    public ICommand ClearFilterCommand { get; }

    public StockFilterViewModel(IStockRepository repo, IKdSignalService kdService)
    {
        _repo = repo ?? throw new ArgumentNullException(nameof(repo));
        _kdService = kdService ?? throw new ArgumentNullException(nameof(kdService));

        View = CollectionViewSource.GetDefaultView(Stocks);
        View.Filter = FilterPredicate;

        LoadFromLocalCommand = new RelayCommand(_ => LoadFromLocal());
        ClearFilterCommand = new RelayCommand(_ => { Query = string.Empty; OnlyGoldenCross = false; });
    }

    private void LoadFromLocal()
    {
        Stocks.Clear();
        foreach (var s in _repo.LoadStocks())
        {
            Stocks.Add(s);
        }
        _ = RefreshGoldenCrossCacheAsync(); // 異步計算 KD 訊號
        View.Refresh();
    }

    private bool FilterPredicate(object obj)
    {
        if (obj is not StockProfile s) return false;

        // 1) 關鍵字搜尋
        if (!string.IsNullOrWhiteSpace(Query))
        {
            var q = Query.Trim();
            if (!s.Code.Contains(q, StringComparison.OrdinalIgnoreCase) &&
                !s.Name.Contains(q, StringComparison.OrdinalIgnoreCase))
                return false;
        }

        // 2) KD 黃金交叉過濾
        if (OnlyGoldenCross)
        {
            if (!_goldenCache.TryGetValue(s.Code, out var isGolden) || !isGolden)
                return false;
        }

        return true;
    }

    // --- KD 快取 ---
    private readonly Dictionary<string, bool> _goldenCache = new();

    private async Task RefreshGoldenCrossCacheAsync()
    {
        _goldenCache.Clear();
        foreach (var s in Stocks)
        {
            bool isGolden = await _kdService.IsGoldenCrossAsync(s.Code);
            _goldenCache[s.Code] = isGolden;
        }
        View.Refresh();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

2) KD 訊號服務

我們沿用昨天的設計,IKdSignalService日線資料計算 KD,判斷是否為黃金交叉。
(此處簡化為示範版本,正式版請接 API 或從本地資料庫讀日線 K 線來算。)

public interface IKdSignalService
{
    Task<bool> IsGoldenCrossAsync(string stockCode);
}
public class DemoKdSignalService : IKdSignalService
{
    public Task<bool> IsGoldenCrossAsync(string stockCode)
    {
        // 這裡應從 LiteDB 或 API 取近 9~20 日收盤價計算 KD
        // 範例:直接回傳 false,請在正式版替換
        return Task.FromResult(false);
    }
}

3) View:XAML 綁定搜尋與 KD 過濾

<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="8" Spacing="8">
    <TextBox Width="200"
             Text="{Binding Query, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
             ToolTip="輸入代號或名稱搜尋"/>
    <CheckBox Content="只顯示 KD 黃金交叉"
              IsChecked="{Binding OnlyGoldenCross, Mode=TwoWay}"/>
    <Button Content="載入本地資料"
            Command="{Binding LoadFromLocalCommand}" Width="120"/>
    <Button Content="清除篩選"
            Command="{Binding ClearFilterCommand}" Width="100"/>
</StackPanel>

<DataGrid ItemsSource="{Binding View}" Margin="8"
          AutoGenerateColumns="False" IsReadOnly="True">
    <DataGrid.Columns>
        <DataGridTextColumn Header="代號" Binding="{Binding Code}" Width="100"/>
        <DataGridTextColumn Header="名稱" Binding="{Binding Name}" Width="200"/>
        <DataGridTextColumn Header="產業" Binding="{Binding Industry}" Width="*"/>
        <DataGridTextColumn Header="更新時間"
                            Binding="{Binding LastUpdatedUtc, StringFormat=\{0:yyyy-MM-dd HH:mm\}}"
                            Width="150"/>
    </DataGrid.Columns>
</DataGrid>

4) 啟動與注入

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var repo = new LiteDbStockRepository("StockData.db");
        var kdService = new DemoKdSignalService(); // 正式版換成日線資料計算
        this.DataContext = new StockFilterViewModel(repo, kdService);
    }
}

執行效果

  1. 點「載入本地資料」 → 從 LiteDB 讀出股票清單,顯示在 DataGrid
  2. 在搜尋框輸入「2330」→ 僅顯示台積電
  3. 勾選「只顯示 KD 黃金交叉」→ 篩選出符合條件的股票(根據 KD 訊號計算結果)

小結

今天我們完成了:

  • LiteDB 載入股票清單
  • 即時搜尋(代號/名稱)
  • KD 黃金交叉條件過濾


上一篇
Day 21 - 儲存資料到 LiteDB
下一篇
Day23 WPF Style 與介面美化
系列文
30天快速上手製作WPF選股工具 — 從C#基礎到LiteDB與Web API整合24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言