سؤال

لقد بدأت للتو في تعلم نمط MVVM لـ WPF.لقد اصطدمت بالحائط: ماذا تفعل عندما تحتاج إلى إظهار OpenFileDialog?

إليك مثال لواجهة المستخدم التي أحاول استخدامها عليها:

alt text

عند النقر فوق زر التصفح، يجب أن يظهر OpenFileDialog.عندما يقوم المستخدم بتحديد ملف من OpenFileDialog، يجب أن يتم عرض مسار الملف في مربع النص.

كيف يمكنني القيام بذلك مع MVVM؟

تحديث:كيف يمكنني القيام بذلك باستخدام MVVM وجعله قابلاً للاختبار؟الحل أدناه لا يعمل لاختبار الوحدة.

هل كانت مفيدة؟

المحلول

ما أفعله عمومًا هو إنشاء واجهة لخدمة تطبيق تؤدي هذه الوظيفة.في الأمثلة الخاصة بي، سأفترض أنك تستخدم شيئًا مثل مجموعة أدوات MVVM أو شيئًا مشابهًا (حتى أتمكن من الحصول على ViewModel الأساسي وRelayCommand).

فيما يلي مثال لواجهة بسيطة للغاية لإجراء عمليات الإدخال والإخراج الأساسية مثل OpenFileDialog وOpenFile.أعرضهما هنا لذا لا تعتقد أنني أقترح عليك إنشاء واجهة واحدة بطريقة واحدة للتغلب على هذه المشكلة.

public interface IOService
{
     string OpenFileDialog(string defaultPath);

     //Other similar untestable IO operations
     Stream OpenFile(string path);
}

في تطبيقك، ستوفر تطبيقًا افتراضيًا لهذه الخدمة.إليك كيف ستستهلكه.

public MyViewModel : ViewModel
{
     private string _selectedPath;
     public string SelectedPath
     {
          get { return _selectedPath; }
          set { _selectedPath = value; OnPropertyChanged("SelectedPath"); }
     }

     private RelayCommand _openCommand;
     public RelayCommand OpenCommand
     {
          //You know the drill.
          ...
     }

     private IOService _ioService;
     public MyViewModel(IOService ioService)
     {
          _ioService = ioService;
          OpenCommand = new RelayCommand(OpenFile);
     }

     private void OpenFile()
     {
          SelectedPath = _ioService.OpenFileDialog(@"c:\Where\My\File\Usually\Is.txt");
          if(SelectedPath == null)
          {
               SelectedPath = string.Empty;
          }
     }
}

هذا بسيط جدًا.والآن للجزء الأخير:قابلية الاختبار.يجب أن يكون هذا واضحًا، لكنني سأوضح لك كيفية إجراء اختبار بسيط لذلك.أنا أستخدم Moq للقطع، ولكن يمكنك استخدام ما تريد بالطبع.

[Test]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
     Mock<IOService> ioServiceStub = new Mock<IOService>();

     //We use null to indicate invalid path in our implementation
     ioServiceStub.Setup(ioServ => ioServ.OpenFileDialog(It.IsAny<string>()))
                  .Returns(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub.Object);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}

ربما هذا سوف يعمل من أجلك.

توجد مكتبة على CodePlex تسمى "SystemWrapper" (http://systemwrapper.codeplex.com) قد يوفر عليك الاضطرار إلى القيام بـ كثير من هذا النوع من الشيء.يبدو أن FileDialog غير مدعوم بعد، لذلك سيتعين عليك بالتأكيد كتابة واجهة لذلك.

أتمنى أن يساعدك هذا.

يحرر:

يبدو أنني أتذكر أنك تفضل TypeMock Isolator لإطار العمل المزيف الخاص بك.إليك نفس الاختبار باستخدام المعزل:

[Test]
[Isolated]
public void OpenFileCommand_UserSelectsInvalidPath_SelectedPathSetToEmpty()
{
    IOService ioServiceStub = Isolate.Fake.Instance<IOService>();

    //Setup stub arrangements
    Isolate.WhenCalled(() => ioServiceStub.OpenFileDialog("blah"))
           .WasCalledWithAnyArguments()
           .WillReturn(null);

     //Setup target and test
     MyViewModel target = new MyViewModel(ioServiceStub);
     target.OpenCommand.Execute();

     Assert.IsEqual(string.Empty, target.SelectedPath);
}

نأمل أن يكون هذا مفيدًا أيضًا.

نصائح أخرى

ال إطار تطبيق WPF (WAF) يوفر تطبيقًا لـ Open وSaveFileDialog.

يوضح نموذج تطبيق Writer كيفية استخدامها وكيف يمكن اختبار الوحدة للكود.

أولاً أنصحك بالبدء بـ مجموعة أدوات WPF MVVM.يمنحك هذا مجموعة رائعة من الأوامر لاستخدامها في مشاريعك.إحدى الميزات المحددة التي أصبحت مشهورة منذ تقديم نمط MVVM هي RelayCommand (هناك العديد من الإصدارات الأخرى بالطبع، لكنني ألتزم فقط بالأكثر استخدامًا).إنه تطبيق لواجهة ICommand التي تسمح لك بوضع أمر جديد في ViewModel الخاص بك.

بالعودة إلى سؤالك، إليك مثال لما قد يبدو عليه ViewModel الخاص بك.

public class OpenFileDialogVM : ViewModelBase
{
    public static RelayCommand OpenCommand { get; set; }
    private string _selectedPath;
    public string SelectedPath
    {
        get { return _selectedPath; }
        set
        {
            _selectedPath = value;
            RaisePropertyChanged("SelectedPath");
        }
    }

    private string _defaultPath;

    public OpenFileDialogVM()
    {
        RegisterCommands();
    }

    public OpenFileDialogVM(string defaultPath)
    {
        _defaultPath = defaultPath;
        RegisterCommands();
    }

    private void RegisterCommands()
    {
        OpenCommand = new RelayCommand(ExecuteOpenFileDialog);
    }

    private void ExecuteOpenFileDialog()
    {
        var dialog = new OpenFileDialog { InitialDirectory = _defaultPath };
        dialog.ShowDialog();

        SelectedPath = dialog.FileName;
    }
}

ViewModelBase و RelayCommand كلاهما من مجموعة أدوات MVVM.إليك ما قد يبدو عليه XAML.

<TextBox Text="{Binding SelectedPath}" />
<Button Command="vm:OpenFileDialogVM.OpenCommand" >Browse</Button>

ورمز XAML.CS الخاص بك خلفك.

DataContext = new OpenFileDialogVM();
InitializeComponent();

هذا كل شيء.

عندما تصبح أكثر دراية بالأوامر، يمكنك أيضًا تعيين الشروط عندما تريد تعطيل زر التصفح، وما إلى ذلك.آمل أن يكون هذا قد وجهك في الاتجاه الذي تريده.

من وجهة نظري فإن الخيار الأفضل هو مكتبة المنشور وطلبات التفاعل.يظل الإجراء الخاص بفتح مربع الحوار داخل xaml ويتم تشغيله من Viewmodel بينما لا يحتاج Viewmodel إلى معرفة أي شيء عن العرض.

أنظر أيضا

https://plainionist.github.io///Mvvm-Dialogs/

على سبيل المثال انظر:

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/PopupCommonDialogAction.cs

https://github.com/plainionist/Plainion.Prism/blob/master/src/Plainion.Prism/Interactivity/InteractionRequest/OpenFileDialogNotification.cs

في رأيي الحل الأفضل هو إنشاء عنصر تحكم مخصص.

يتكون عنصر التحكم المخصص الذي أقوم بإنشائه عادةً من:

  • مربع نص أو كتلة نصية
  • زر مع صورة كقالب
  • خاصية تبعية السلسلة حيث سيتم التفاف مسار الملف إليها

لذا فإن ملف *.xaml سيكون بهذا الشكل

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    <Button Grid.Column="1" Click="Button_Click">
        <Button.Template>
            <ControlTemplate>
                <Image Grid.Column="1" Source="../Images/carpeta.png"/>
            </ControlTemplate>                
        </Button.Template>
    </Button>        
</Grid>

وملف *.cs:

public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text",
        typeof(string),
        typeof(customFilePicker),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault | FrameworkPropertyMetadataOptions.Journal));

public string Text
{
    get
    {
        return this.GetValue(TextProperty) as String;
    }
    set
    {
        this.SetValue(TextProperty, value);
    }
}

public FilePicker()
{
    InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    OpenFileDialog openFileDialog = new OpenFileDialog();

    if(openFileDialog.ShowDialog() == true)
    {
        this.Text = openFileDialog.FileName;
    }
}

في النهاية يمكنك ربطه بنموذج العرض الخاص بك:

<controls:customFilePicker Text="{Binding Text}"/>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top