当 ListView 不包含 UWP 中带有 MVVM 的项目时显示消息

Display message when ListView contains no items with MVVM in a UWP

这是我的第一个 MVVM 项目,我需要编写用于在视图中操作控件的代码似乎比它必须的复杂得多。

我发现很难完全理解 MVVM 并决定何时可以将代码放在后面。

基本上我的问题是我想显示一条消息,告诉用户当列表视图绑定到的 ObservableCollection 不包含任何项目时,它是空的。这个想法是在视图中有一个 TextBlock,并且只有在没有要显示的项目时(在用户创建项目之前和删除之后)才将其可见性 属性 设置为 Visible所有项目)

我无法使用此解决方案,因为 UWP 不支持 BooleanToVisibilityConverter: WPF MVVM hiding button using BooleanToVisibilityConverter

查看:

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:EventMaker3000.View"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ViewModel="using:EventMaker3000.ViewModel"
    xmlns:Interactivity="using:Microsoft.Xaml.Interactivity" xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
    x:Class="EventMaker3000.View.EventPage"
    mc:Ignorable="d">
    <Page.BottomAppBar>
        <CommandBar>
            <CommandBar.Content>
                <Grid/>
            </CommandBar.Content>
            <AppBarButton Icon="Delete" Label="Delete" IsEnabled="{Binding DeletebuttonEnableOrNot}">
                <Interactivity:Interaction.Behaviors>
                    <Core:EventTriggerBehavior EventName="Click">
                        <Core:NavigateToPageAction/>
                        <Core:InvokeCommandAction Command="{Binding DeleteEventCommand}"/>
                    </Core:EventTriggerBehavior>
                </Interactivity:Interaction.Behaviors>
            </AppBarButton>
            <AppBarButton Icon="Add" Label="Add">
                <Interactivity:Interaction.Behaviors>
                    <Core:EventTriggerBehavior EventName="Click">
                        <Core:NavigateToPageAction TargetPage="EventMaker3000.View.CreateEventPage"/>
                    </Core:EventTriggerBehavior>
                </Interactivity:Interaction.Behaviors>
            </AppBarButton>
        </CommandBar>
    </Page.BottomAppBar>

    <Page.DataContext>
        <ViewModel:EventViewModel/>
    </Page.DataContext>

    <Grid Background="WhiteSmoke">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <!--Header-->
        <TextBlock 
            Text="Events" 
            Foreground="Black" 
            Margin="0,20,0,0" 
            Style="{ThemeResource HeaderTextBlockStyle}" 
            HorizontalAlignment="center" 
            VerticalAlignment="Center"/>

        <ListView
            ItemsSource="{Binding EventCatalogSingleton.Events, Mode=TwoWay}"
            SelectedItem="{Binding SelectedEvent, Mode=TwoWay}"
            Grid.Row="1"    
            Background="WhiteSmoke"
            Padding="0,30,0,0">
            <Interactivity:Interaction.Behaviors>
                <Core:EventTriggerBehavior EventName="SelectionChanged">
                    <Core:InvokeCommandAction Command="{Binding EnableOrNotCommand}"/>
                </Core:EventTriggerBehavior>
            </Interactivity:Interaction.Behaviors>

            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid VerticalAlignment="Center" Margin="5,0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>

                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                            <RowDefinition Height="Auto"/>
                        </Grid.RowDefinitions>

                        <TextBlock Grid.Column="0"
                            Grid.Row="0"
                            Margin="5" 
                            Text="{Binding Name, Mode=TwoWay}" 
                            Style="{ThemeResource TitleTextBlockStyle}" Foreground="Black"/>
                        <TextBlock Grid.Column="1"
                            Grid.Row="1"
                            Margin="5" 
                            Text="{Binding Place, Mode=TwoWay}"
                            HorizontalAlignment="Right"
                            Style="{ThemeResource CaptionTextBlockStyle}" Foreground="Black"/>
                        <TextBlock Grid.Column="0"
                            Grid.Row="2"
                            Margin="5" 
                            Text="{Binding Description, Mode=TwoWay}"
                            Style="{ThemeResource BodyTextBlockStyle}" Foreground="Black"/>

                        <TextBlock Grid.Column="0"
                            Grid.Row="1" 
                            Margin="5" 
                            Text="{Binding DateTime, Mode=TwoWay}" 
                            Style="{ThemeResource CaptionTextBlockStyle}" Foreground="Black"/>
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>

            <!--Sets each listview item to stretch-->
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>

        <!-- TextBlock for empty list view-->

        <TextBlock 
            Grid.Row="1"
            Margin="5,5,5,5"  
            VerticalAlignment="Center"  
            HorizontalAlignment="Center"  
            Text="You have no events"  
            Style="{StaticResource BaseTextBlockStyle}"  
            Visibility="{Binding TextBlockVisibility}"/>
    </Grid>
</Page>

ViewModel:

public class EventViewModel:INotifyPropertyChanged {

private bool _deleteButtonEnableOrNot = false;
private ICommand _enableOrNotCommand;

//TextBlock
private string _textBlockVisibility = "Visible";
private ICommand _textBlockVisibilityCommand;


public EventCatalogSingleton EventCatalogSingleton { get; set; }
public Handler.EventHandler EventHandler { get; set; }

// Disable or enable Deletebutton
public bool DeletebuttonEnableOrNot
{
    get { return _deleteButtonEnableOrNot;}
    set
    {
        _deleteButtonEnableOrNot = value;
        OnPropertyChanged();
    }            
}

public ICommand EnableOrNotCommand
{
    get { return _enableOrNotCommand; }
    set { _enableOrNotCommand = value; }
}

// Set TextBlock visibility
public string TextBlockVisibility
{
    get { return _textBlockVisibility; }
    set
    {
        _textBlockVisibility = value;
        OnPropertyChanged();
    }
}

public ICommand TextBlockVisibilityCommand
{
    get { return _textBlockVisibilityCommand; }
    set { _textBlockVisibilityCommand = value; }
}

// Constructor
public EventViewModel()
{
    //Initializes Date and Time with some values that are bound to controls.
    DateTime dt = System.DateTime.Now;
    _date = new DateTimeOffset(dt.Year, dt.Month, dt.Day, 0, 0, 0, 0, new TimeSpan());
    _time = new TimeSpan(dt.Hour, dt.Minute, dt.Second);

    EventCatalogSingleton = EventCatalogSingleton.getInstance();
    EventHandler = new Handler.EventHandler(this);

    // Creates an instance of the RelayCommand and passes necessary method as a parameter
    _createEventCommand = new RelayCommand(EventHandler.CreateEvent);

    _deleteEventCommand = new RelayCommand(EventHandler.GetDeleteConfirmationAsync);

    _enableOrNotCommand = new RelayCommand(EventHandler.EnableOrNot);

    _textBlockVisibilityCommand = new RelayCommand(EventHandler.TextBlockVisibility);

}

单例:

public class EventCatalogSingleton { 私人静态 EventCatalogSingleton _instance;

private EventCatalogSingleton()
{
    Events = new ObservableCollection<Event>();

    // Creates instances of events and adds it to the observable collection.
    LoadEventAsync();
}

//Checks if an instance already exists, if not it will create one. Makes sure we only have one instance
public static EventCatalogSingleton getInstance()
{
    if (_instance != null)
    {
        return _instance;
    }
    else
    {
        _instance = new EventCatalogSingleton();
        return _instance;
    }
}

// Creates the observable collection
public ObservableCollection<Event> Events { get; set; }

public void AddEvent(Event newEvent)
{
    Events.Add(newEvent);
    PersistencyService.SaveEventsAsJsonAsync(Events);
}

public void AddEvent(int id, string name, string description, string place, DateTime date)
{
    Events.Add(new Event(id, name, description, place, date));
    PersistencyService.SaveEventsAsJsonAsync(Events);
}


public void RemoveEvent(Event myEvent)
{
    Events.Remove(myEvent);
    PersistencyService.SaveEventsAsJsonAsync(Events);
}

public async void LoadEventAsync()
{

    var events = await PersistencyService.LoadEventsFromJsonAsync();
    if (events != null)
        foreach (var ev in events)
        {
            Events.Add(ev);
        }

}

}

处理程序:

public class 事件处理器 {

public EventViewModel EventViewModel { get; set; }

public EventHandler(EventViewModel eventViewModel)
{
    EventViewModel = eventViewModel;
}

public void CreateEvent()
{
    EventViewModel.EventCatalogSingleton.AddEvent(EventViewModel.Id,    EventViewModel.Name, EventViewModel.Description, EventViewModel.Place, DateTimeConverter.DateTimeOffsetAndTimeSetToDateTime(EventViewModel.Date, EventViewModel.Time));
}


private void DeleteEvent()
{
    EventViewModel.EventCatalogSingleton.Events.Remove(EventViewModel.SelectedEvent);
}

// Confirmation box that prompts user before deletion
public async void GetDeleteConfirmationAsync()
{
    MessageDialog msgbox = new MessageDialog("Are you sure you want to permenantly delete this event?", "Delete event");

    msgbox.Commands.Add(new UICommand
    {
        Label = "Yes",
        Invoked = command => DeleteEvent()
    }
    );

    msgbox.Commands.Add(new UICommand
    {
        Label = "No",
    }
    );
    msgbox.DefaultCommandIndex = 1;
    msgbox.CancelCommandIndex = 1;
    msgbox.Options = MessageDialogOptions.AcceptUserInputAfterDelay;

    await msgbox.ShowAsync();
}

public void EnableOrNot()
{
    EventViewModel.DeletebuttonEnableOrNot = EventViewModel.DeletebuttonEnableOrNot = true;
}

public void TextBlockVisibility()
{
    if (EventViewModel.EventCatalogSingleton.Events.Count < 1)
    {
        EventViewModel.TextBlockVisibility = EventViewModel.TextBlockVisibility = "Visible";
    }        
}

}

要包含的代码很多,我知道 - 不知道要省略什么。 我包含了当我在列表视图中选择了一个项目时启用删除按钮的代码 - 它工作正常。

为什么删除列表视图中的所有项目后视图中的TextBlock 不显示?我真的有必要在视图模型中拥有属性和 ICommand 以更改视图中控件的外观和其他内容吗?

我已经成功地在 StackPanel 中为我的这样一个项目添加了可见性绑定

模型cs

    Visibility showPanel = Visibility.Collapsed;
    public Visibility ShowPanel
    {
        get
        {
            return showPanel;
        }

        set
        {
            showPanel = value;
            NotifyPropertyChanged("ShowPanel");
        }
    }

XAML

  <StackPanel Height="220" Orientation="Vertical" Visibility="{Binding ShowPanel}">

还有很多方法可以显示消息,例如

  1. 您可以将一个 TextBlock 绑定到一个错误“”,当它为空时添加您的错误

  2. 从模型中创建 DialogMessage

        await Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
          {
              MessageDialog dialog = new MessageDialog(error);
              await dialog.ShowAsync();
          });
    

首先,您想尽最大努力在视图和视图模型之间保持清晰的关注点分离。因此,尽量不要包含 UI 特定类型,例如 VisibilityMessageDialog。您可以为负责显示对话框的 MessageDialog 创建一个界面,然后将其传递给您的视图模型。

其次,您应该准备编写自己的值转换器 (BooleanToVisibilityConverter),如下所示:

public sealed class BooleanToVisibilityConverter : IValueConverter
{
    public object Convert(object value, 
        Type targetType, object parameter, string language)
    {
        bool isVisible = (bool)value;
        return isVisible ? Visibility.Visible : Visibility.Collapsed;
    }

    public object ConvertBack(object value, 
           Type targetType, object parameter, string language)
    {
        return (Visibility)value == Visibility.Visible;
    }
}

并像这样在您的视图中使用它:

<Page
   xmlns:converters="using:MyApp.Whatever">
    <Page.Resources>
        <converters:BooleanToVisibilityConverter x:Key="converter"/>
    </Page.Resources>
    <TextBlock
    Visibility="{Binding HasNoItems, Mode=TwoWay, 
        Converter={StaticResource converter}}">
    </TextBlock>
</Page>

并在您的虚拟机中:

public bool HasNoItems
{
    get { return this.hasNoItems; }
    set { this.hasNoItems = value; OnPropertyChanged(); }
}

很有趣,但我和 Daren May 刚刚在 Microsoft Virtual Academy 上教授了一门专门针对此的免费课程。这对你来说可能是一个很好的资源。观看视频 #2 @ 13 分钟。

https://mva.microsoft.com/en-US/training-courses/xaml-for-windows-10-items-controls-14483

看看这个简单的方法:

使用此代码:

class VisibleWhenZeroConverter : IValueConverter
{
    public object Convert(object v, Type t, object p, string l) =>
        Equals(0d, (double)v) ? Visibility.Visible : Visibility.Collapsed;

    public object ConvertBack(object v, Type t, object p, string l) => null;
}

还有这个XAML:

    <StackPanel.Resources>
        <cvt:VisibleWhenZeroConverter x:Name="VisibleWhenZeroConverter" />
    </StackPanel.Resources>

    <ListView ItemsSource="{x:Bind Items}" x:Name="MyList">
        <ListView.Header>
            <TextBlock Visibility="{Binding Items.Count, ElementName=MyList, 
                       Converter={StaticResource VisibleWhenZeroConverter}}">
                <Run Text="There are no items." />
            </TextBlock>
        </ListView.Header>
    </ListView>

有道理吗?希望如此。

PS: this answers the EXACT title of your question. Hope it helps.

祝你好运!