Свернуть / Видимые пользовательские элементы управления при нажатии кнопки с помощью MVVM - без механизма подкачки -
-
21-09-2019 - |
Вопрос
В моем сценарии у меня есть MainView + MainViewModel, UserControl1 + UserControl 2.В MainView у меня есть 2 кнопки с надписью:Button_ShowUserControl1 + Button_ShowUserControl2.В нижней части MainView у меня есть "ContentGrid", который принимает /should_take ...каждый пользовательский элемент управления.
Моя цель:
При нажатии кнопки Button_ShowUserControl1 Пользовательский контроль1 является Видимый и Пользовательский контроль2 или любой другой пользовательский элемент управления должен быть установлен в Рухнул.То же самое справедливо для Button_ShowUserControl2.
Моя проблема:
1.) Поскольку пользовательские элементы управления должны быть загружены при запуске приложения, как я могу объединить их все в одну "ContentGrid"?На самом деле это невозможно...итак, как я могу сделать один пользовательский элемент управления видимым, в то время как другой находится в том же месте / "ContentGrid" только что рухнул?
2.) Поскольку 1.) кажется невозможным, как я могу создать экземпляр всех пользовательских элементов управления при запуске приложения и сделать их видимыми / свернутыми только при нажатии соответствующей кнопки?
3.) Поскольку UserControl имеет свойство Visibility = Visible / Hidden / Collapsed , как я могу привязаться к свойству в ViewModel, возвращающему такое значение, как Collapsed?Я мог бы получить только логическое значение типа Visibility = false / true?
Мой тестовый код:
<Grid x:Name="LayoutRoot" Background="#FFBDF5BD" ShowGridLines="False">
<Grid.RowDefinitions>
<RowDefinition Height="96*" />
<RowDefinition Height="289*" />
</Grid.RowDefinitions>
<Grid HorizontalAlignment="Stretch" Name="MenuGrid" VerticalAlignment="Stretch" Background="#FFCECEFF">
<StackPanel Name="stackPanel1" Background="#FFEDFF00" Orientation="Horizontal">
<Button Content="User Data 1" Height="35" Name="button1" Command="{Binding Path=ShowUserControl1Command}" Width="150" Margin="100,0,0,0" />
<Button Content="User Data 2" Height="35" Name="button2" Width="150" Margin="100,0,0,0" />
</StackPanel>
</Grid>
<Grid Grid.Row="1" HorizontalAlignment="Stretch" Name="ContentGrid" VerticalAlignment="Stretch" Background="#FFB15454" />
</Grid>
<UserControl x:Class="SwapUserControls.MVVM.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:SwapUserControls.MVVM.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Visibility="{Binding IsUserControl1Collapsed, Path=Value}">
<UserControl.Resources>
<vm:MainViewModel x:Key="MainViewModelID" />
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource MainViewModelID}" />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="228*" />
<RowDefinition Height="72*" />
</Grid.RowDefinitions>
<Button Content="UserControl2" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="112,27,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
<DataGrid HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" Background="#FFC046F8" />
</Grid>
public class MainViewModel : ViewModelBase
{
RelayCommand _ShowUserControl1Command;
private bool _IsUserControl1Collapsed;
public RelayCommand ShowUserControl1Command
{
get
{
if (_ShowUserControl1Command == null)
{
_ShowUserControl1Command = new RelayCommand( () => ShowUserControl1() );
}
return _ShowUserControl1Command;
}
}
public void ShowUserControl1()
{
_IsUserControl1Collapsed = true;
}
public bool IsUserControl1Collapsed
{
get
{
return _IsUserControl1Collapsed;
}
}
}
Да, код неправильный, поэтому я спрашиваю здесь :)
Решение
У вас есть только 2 ошибки в этом коде.
1) Вы не можете установить видимость пользовательского элемента управления напрямую...вы должны установить его на контейнер:
<Grid Visibility="Collapsed">
<myControls:MyUserControl />
</Grid>
2) Видимость - это не логическое значение, это перечисление.Таким образом, вам нужно будет использовать конвертер для преобразования из логического значения в видимость.Наблюдать:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid Visibility="{Binding ShouldShowUsercontrol1, Converter={StaticResource BoolToVis}}">
<myControls:MyUserControl />
</Grid>
</Window>
Так и должно быть.Надеюсь, это поможет.
Есть и другие вещи, о которых вы оставляете подсказки, которые могут повлиять на способность этого работать.Например, вы не показываете самый большой элемент контейнера...вы оборачиваете все в StackPanel?Например, если вы оборачиваете все в сетку, элементы управления будут накладывать все слоями.
Попробуйте эти изменения, которые я предлагаю...это должно приблизить вас.
Редактировать:Еще одна идея с использованием шаблонов данных
Еще одна вещь, которую вы могли бы сделать, это убедиться, что у вас есть уникальная ViewModel для каждого из этих представлений, которые вы хотите показать и скрыть:
public class MyFirstViewModel : ViewModel
{
}
public class MySecondViewModel : ViewModel
{
}
Затем из вашей "родительской" или "основной" ViewModel вы показываете или скрываете нужные вам представления, поскольку они есть в коллекции ViewModels:
public MyMainViewModel : ViewModel
{
public ObservableCollection<ViewModel> ViewsToShow
{
...
}
public void ShowFirstViewModel()
{
ViewsToShow.Add(new MyFirstViewModel());
}
}
Чтобы связать все в вашем представлении, вы бы затем добавили datatemplate этим типам с их пользовательскими элементами управления (но это не привело бы к созданию экземпляров этих представлений, если бы они не были необходимы:
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type myViewModels:MyFirstViewModel}">
<myViews:MyFirstView />
</DataTemplate>
<DataTemplate DataType="{x:Type myViewModels:MySecondViewModel}">
<myViews:MySecondView />
</DataTemplate>
</Window.Resources>
<ItemsControl ItemsSource="{Binding ViewsToShow}" />
</Window>
И для любых ViewModels, которые вы добавляете в "ViewsToShow", представление автоматически увидит это и шаблон в соответствующем представлении.Опять же, без создания его экземпляра до того, как это потребуется.
Вероятно, это немного чище, чем помещать все по отдельности в представление и настраивать видимость, но это будет зависеть от того, есть ли у вас уникальный тип модели представления для каждого представления, что может быть не так.
Вопрос о сохранении состояния возникает при использовании подхода с привязкой к данным.Решение здесь состоит в том, чтобы использовать вашу ViewModel в качестве состояния элемента управления и соответствующим образом спроектировать как ваши ViewModels, так и ваши представления.Вот пример, который позволяет вам менять местами ваши представления с помощью DataTemplating, но переключение туда и обратно сохраняет состояние.
Предположим, у вас есть настройка из последнего раздела с 2 viewmodels, в которых определены datatemplates.Давайте немного изменим MainViewModel:
public MyMainViewModel : ViewModel
{
public RelayCommand SwapViewsCommand
{
...
}
public ViewModel View
{
...
}
private ViewModel _hiddenView;
public MyMainViewModel()
{
View = new MyFirstViewModel();
_hiddenView = new MySecondViewModel();
SwapViewsCommand = new RelayCommand(SwapViewModels);
}
public void SwapViewModels()
{
var hidden = _hiddenView;
_hiddenView = View;
View = hidden;
}
}
И несколько изменений в основном виде.Я опустил таблички с данными для краткости.
<Window ...>
<!-- DataTemplates Here -->
<Button Command="{Binding SwapViewsCommand}">Swap!</Button>
<ContentControl Content="{Binding View}" />
</Window>
Вот и все.Секрет здесь в том, что я сохраняю ссылку на исходную модель представления.Таким образом, предположим, что в viewmodel есть свойство string и связанное с ним текстовое поле в DataTemplated usercontrol с двусторонняя привязка тогда состояние, по существу, будет сохранено.