You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

切换ComboBox时填充ListView的正确MVVM实现方案

Hey there! Let's walk through exactly how to refactor your non-MVVM WPF code into a clean, maintainable MVVM implementation for your Jira user lookup feature. You already know the basics (RelayCommand, INotifyPropertyChanged), so I'll tie those directly to your use case with concrete examples.

Step 1: Define Your Data Models

First, create simple model classes to represent the data you're working with—this keeps your data separate from UI and logic:

// Represents a Jira user account for the ComboBox
public class JiraUser
{
    public string AccountName { get; set; }
}

// Represents a Jira issue returned from your API call
public class JiraIssue
{
    public string Key { get; set; }
    public string Summary { get; set; }
    public string Status { get; set; }
    // Add any other fields from Jira's response that you need to display
}
Step 2: Build the Core ViewModel

This is the heart of your MVVM setup. It'll hold your data, handle commands, and notify the UI of changes. First, let's implement a basic RelayCommand (if you don't have one already):

public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Func<object, bool> _canExecute;

    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }

    public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public bool CanExecute(object parameter) => _canExecute?.Invoke(parameter) ?? true;
    public void Execute(object parameter) => _execute(parameter);
}

Now the main ViewModel, implementing INotifyPropertyChanged:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;

public class JiraLookupViewModel : INotifyPropertyChanged
{
    private ObservableCollection<JiraUser> _jiraUsers;
    private JiraUser _selectedUser;
    private ObservableCollection<JiraIssue> _searchResults;
    private bool _isLoading;

    // Command for triggering the Jira search
    public ICommand SearchJiraCommand { get; }

    public JiraLookupViewModel()
    {
        // Initialize collections (ObservableCollection auto-updates UI when changed)
        JiraUsers = new ObservableCollection<JiraUser>();
        SearchResults = new ObservableCollection<JiraIssue>();
        
        // Wire up the command to our search logic
        SearchJiraCommand = new RelayCommand(async _ => await ExecuteSearchAsync());
    }

    // Properties with UI change notifications
    public ObservableCollection<JiraUser> JiraUsers
    {
        get => _jiraUsers;
        set
        {
            _jiraUsers = value;
            OnPropertyChanged();
        }
    }

    public JiraUser SelectedUser
    {
        get => _selectedUser;
        set
        {
            _selectedUser = value;
            OnPropertyChanged();
            // Optional: Auto-trigger search when a user is selected
            // SearchJiraCommand.Execute(null);
        }
    }

    public ObservableCollection<JiraIssue> SearchResults
    {
        get => _searchResults;
        set
        {
            _searchResults = value;
            OnPropertyChanged();
        }
    }

    public bool IsLoading
    {
        get => _isLoading;
        set
        {
            _isLoading = value;
            OnPropertyChanged();
        }
    }

    // Load initial Jira user list (replace with your existing user fetch logic)
    public async Task LoadJiraUsersAsync()
    {
        IsLoading = true;
        try
        {
            // Replace this with your actual code to get Jira accounts
            var fetchedUsers = await YourJiraService.GetUserAccountsAsync();
            
            JiraUsers.Clear();
            foreach (var user in fetchedUsers)
            {
                JiraUsers.Add(new JiraUser { AccountName = user.AccountName });
            }
        }
        catch (Exception ex)
        {
            // Handle errors (consider using a ViewModel error property instead of MessageBox for pure MVVM)
            MessageBox.Show($"Failed to load users: {ex.Message}");
        }
        finally
        {
            IsLoading = false;
        }
    }

    // Execute the Jira search using the selected user
    private async Task ExecuteSearchAsync()
    {
        if (SelectedUser == null)
        {
            MessageBox.Show("Please select a user first!");
            return;
        }

        IsLoading = true;
        SearchResults.Clear();
        try
        {
            // Replace this with your existing Jira API call logic
            var issues = await YourJiraService.SearchIssuesByUser(SelectedUser.AccountName);
            
            foreach (var issue in issues)
            {
                SearchResults.Add(new JiraIssue
                {
                    Key = issue.Key,
                    Summary = issue.Summary,
                    Status = issue.Status.Name
                });
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show($"Search failed: {ex.Message}");
        }
        finally
        {
            IsLoading = false;
        }
    }

    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Note: YourJiraService is a new class you'll create to extract all your existing Jira API logic from the UI code-behind. This service handles all data fetching, so your ViewModel doesn't touch any UI-specific code.

Step 3: Update Your XAML View

Bind your ComboBox and ListView directly to the ViewModel properties and commands. Add a loading indicator for better UX:

<Window x:Class="YourNamespace.JiraLookupWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:YourNamespace"
        Title="Jira User Lookup" Height="450" Width="800">
    <Window.Resources>
        <!-- Converters for UI state binding -->
        <local:BooleanNegationConverter x:Key="BooleanNegationConverter"/>
        <local:NullToBooleanConverter x:Key="NullToBooleanConverter"/>
        <local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
    </Window.Resources>
    <Window.DataContext>
        <!-- Attach the ViewModel to the View -->
        <local:JiraLookupViewModel />
    </Window.DataContext>

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!-- ComboBox for Jira Users -->
        <ComboBox Grid.Row="0"
                  ItemsSource="{Binding JiraUsers}"
                  SelectedItem="{Binding SelectedUser}"
                  DisplayMemberPath="AccountName"
                  Margin="0 0 0 10"
                  IsEnabled="{Binding IsLoading, Converter={StaticResource BooleanNegationConverter}}"/>

        <!-- Search Button (remove if using auto-search on selection) -->
        <Button Grid.Row="1"
                Content="Search Jira"
                Command="{Binding SearchJiraCommand}"
                IsEnabled="{Binding SelectedUser, Converter={StaticResource NullToBooleanConverter}}"
                Margin="0 0 0 10"/>

        <!-- Results ListView -->
        <ListView Grid.Row="2"
                  ItemsSource="{Binding SearchResults}"
                  IsEnabled="{Binding IsLoading, Converter={StaticResource BooleanNegationConverter}}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Issue Key" DisplayMemberBinding="{Binding Key}" Width="100"/>
                    <GridViewColumn Header="Summary" DisplayMemberBinding="{Binding Summary}" Width="400"/>
                    <GridViewColumn Header="Status" DisplayMemberBinding="{Binding Status}" Width="100"/>
                </GridView>
            </ListView.View>
        </ListView>

        <!-- Loading Indicator -->
        <ProgressBar Grid.Row="0" Grid.RowSpan="3"
                     IsIndeterminate="{Binding IsLoading}"
                     Visibility="{Binding IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"
                     Height="20"/>
    </Grid>
</Window>

You'll need these simple converter classes for the UI state bindings:

public class BooleanNegationConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => !(bool)value;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => !(bool)value;
}

public class NullToBooleanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => value != null;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => throw new NotImplementedException();
}

public class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        => (bool)value ? Visibility.Visible : Visibility.Collapsed;

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        => (Visibility)value == Visibility.Visible;
}
Step 4: Refactor Your Existing Code
  1. Extract Jira Service Logic: Move all your existing code that calls Jira APIs, fetches user lists, and queries issues into a standalone JiraService class. This class should have no references to WPF UI elements.
  2. Initialize the ViewModel: Update your Window's code-behind to load the initial user list:
    public JiraLookupWindow()
    {
        InitializeComponent();
        var viewModel = (JiraLookupViewModel)DataContext;
        _ = viewModel.LoadJiraUsersAsync();
    }
    
  3. Remove Old Event Handlers: Delete any SelectionChanged or Click event handlers from your code-behind—binding and commands handle this now.
Key MVVM Benefits You're Getting
  • Separation of Concerns: UI (View) only handles display, ViewModel handles logic, Model handles data.
  • Testability: You can write unit tests for the ViewModel's search/load logic without needing a UI.
  • Maintainability: Changes to the UI don't require touching logic, and vice versa.

内容的提问来源于stack exchange,提问作者Monstryyy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:39:26