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

基于MVVM模式的WPF+C#拍照按钮实现故障求助

问题分析与解决方案

核心问题

你的代码并没有真正遵循MVVM模式,同时存在几个关键错误导致功能失效:

  1. ViewModel与View耦合:把ViewModel的逻辑直接写在MainWindow(View)类中,违背了MVVM分离视图与业务逻辑的原则。
  2. DataContext未绑定:View没有指定DataContext,导致Command="{Binding TakePhotoCommand}"无法找到对应的命令。
  3. 同步网络请求阻塞UI:request.GetResponse()是同步方法,会卡住UI线程,甚至可能导致操作无响应。
  4. 流重复读取冲突:同时用StreamReader和Bitmap读取同一个响应流,会导致流指针位置混乱,最终Bitmap读取失败。

修复步骤

1. 实现标准的RelayCommand

如果没有现成的RelayCommand,先创建一个通用实现:

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

    public event EventHandler? CanExecuteChanged;

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

    public bool CanExecute(object? parameter)
    {
        return _canExecute?.Invoke() ?? true;
    }

    public void Execute(object? parameter)
    {
        _execute();
    }

    public void RaiseCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

2. 创建独立的ViewModel类

将业务逻辑移到单独的ViewModel中,并实现异步操作:

using System.ComponentModel;
using System.Net.Http;
using System.Windows;
using System.Windows.Input;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;

public class MainViewModel : INotifyPropertyChanged
{
    public ICommand TakePhotoCommand { get; }

    public MainViewModel()
    {
        TakePhotoCommand = new RelayCommand(async () => await TakePhotoAsync());
    }

    private async Task TakePhotoAsync()
    {
        try
        {
            using var httpClient = new HttpClient();
            // 发送GET请求获取图片流
            using var responseStream = await httpClient.GetStreamAsync("http://你的IP和端口/TakePicture");
            
            // 直接从流加载Bitmap,不要用StreamReader读取同一个流
            using var bitmap = new Bitmap(responseStream);
            bitmap.RotateFlip(RotateFlipType.Rotate90FlipNone);
            
            // 确保保存路径存在
            var savePath = @"C:\指定路径\照片名称.jpg";
            var directory = Path.GetDirectoryName(savePath);
            if (!Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory!);
            }
            
            bitmap.Save(savePath, ImageFormat.Jpeg);
            
            // MVVM规范中建议通过属性通知View显示提示,这里用Dispatcher临时处理UI交互
            Application.Current.Dispatcher.Invoke(() => 
                MessageBox.Show("照片保存成功"));
        }
        catch (Exception ex)
        {
            Application.Current.Dispatcher.Invoke(() => 
                MessageBox.Show($"操作失败:{ex.Message}"));
        }
    }

    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

3. 修改View(MainWindow)

移除View中的业务逻辑,设置DataContext:

<Window x:Class="WpfApp1.MainWindow"
        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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <!-- 设置DataContext为ViewModel -->
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <TextBlock Text="Press button" FontSize="30" Margin="50" TextAlignment="Center"/>
            <Button Content="Tomar Foto" Command="{Binding TakePhotoCommand}" Margin="5 0 5 5"  Width="200" FontSize="25" Padding="15 3"/>
        </StackPanel>
    </Grid>
</Window>

同时修改MainWindow的后台代码,清空多余逻辑:

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

关键说明

  • MVVM分离:ViewModel不再依赖View,所有业务逻辑都在ViewModel中,View只负责显示和绑定。
  • 异步操作:用HttpClient.GetStreamAsync替代同步的HttpWebRequest,避免阻塞UI线程,提升用户体验。
  • 流处理优化:直接从响应流加载Bitmap,不再重复读取同一个流,避免流位置冲突。
  • 路径安全:添加了目录检查,确保保存路径存在,避免因目录不存在导致保存失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 07:00:59