I'm trying to bind menuflyoutitem of choosen item in listview to Delete Command. Flyoutmenu shows when I'm holding element on list, so I can't bind it to SelectedItem property in viewmodel.
SelectedItem property works fine, but i have to tap element first and then hold item for showing menu and then delete. How can i pass sender from Holding to my property in viewmodel?
View:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="My List App"
HorizontalAlignment="Center"
Style="{ThemeResource HeaderTextBlockStyle}" />
<ListView x:Name="myListView"
Grid.Row="1"
ItemsSource="{Binding AllMyLists}"
SelectedItem="{Binding SelectedList, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Holding">
<controls:OpenMenuFlyoutAction />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<FlyoutBase.AttachedFlyout>
<MenuFlyout>
<MenuFlyoutItem Text="Delete"
Command="{Binding ElementName=myListView, Path=DataContext.DeleteEntryListCommand}" />
</MenuFlyout>
</FlyoutBase.AttachedFlyout>
<TextBlock Text="{Binding Name}"
Style="{ThemeResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
OpenMenuFlyoutAction for used for showing flyoutMenu:
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
public object Execute(object sender, object parameter)
{
FlyoutBase.ShowAttachedFlyout((FrameworkElement)sender);
return sender;
}
}
And My ViewModel:
public class AllListsPageViewModel : Microsoft.Practices.Prism.Mvvm.ViewModel, Interfaces.IAllListsPageViewModel
{
#region Fields
private ObservableCollection<EntryList> _allMyLists;
private EntryList _selectedList;
private DelegateCommand _addEntryListCommand;
private DelegateCommand _deleteEntryListCommand;
private readonly INavigationService _navigationService;
#endregion //Fields
#region Construction
public AllListsPageViewModel(INavigationService navigationService) { ... }
#endregion //Construction
#region Properties
public ObservableCollection<EntryList> AllMyLists
{
get { return _allMyLists; }
set { SetProperty(ref _allMyLists, value); }
}
public EntryList SelectedList
{
get { return _selectedList; }
set { SetProperty(ref _selectedList, value); }
}
#endregion //Properties
#region Methods
private void loadData() { }
private bool _canAddEntryList() { return true; }
private void _addEntryList() { ... }
private bool _canDeleteEntryList() { ... }
private void _deleteEntryList()
{
//How to get sender from holding event here?
_allMyLists.Remove(_selectedList);
}
#endregion //Methods
#region Commands
public ICommand AddEntryListCommand { ... }
public ICommand DeleteEntryListCommand
{
get
{
if (_deleteEntryListCommand == null)
{
_deleteEntryListCommand = new DelegateCommand(_deleteEntryList, _canDeleteEntryList);
}
return _deleteEntryListCommand;
}
}
#endregion //Commands
}
Thanks in advance.
I had the same problem today and I have resolved as follows:
namespace your.namespace
{
using Microsoft.Xaml.Interactivity;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Input;
public class OpenMenuFlyoutAction : DependencyObject, IAction
{
private static object holdedObject;
public object Execute(object sender, object parameter)
{
FrameworkElement senderElement = sender as FrameworkElement;
FlyoutBase flyoutBase = FlyoutBase.GetAttachedFlyout(senderElement);
flyoutBase.ShowAt(senderElement);
var eventArgs = parameter as HoldingRoutedEventArgs;
if (eventArgs == null)
{
return null;
}
var element = eventArgs.OriginalSource as FrameworkElement;
if (element != null)
{
HoldedObject = element.DataContext;
}
return null;
}
public static object HoldedObject
{
get { return holdedObject; }
set
{
holdedObject = value;
}
}
}
}
Then you can access the object as follows:
var foo = OpenMenuFlyoutAction.HoldedObject as Foo;
I think it's not bad solution that the HoldedObject is static as you can not do hold two items at the same time.
Related
I have a listbox with a couple of buttons underneath.
<ListBox ItemsSource="{Binding SongList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding addSongCommand}" />
<Button Content="Remove"/>
</StackPanel>
SongList1
When I click the add button I want the add and remove buttons to be replaced by a textbox and submit button.
SongList2
Then when I click submit i want it to add the entered string into the collection (SongList) and bring back the add and remove buttons.
SongList3
How would the hiding and showing of controls be done with MVVM? Assuming that I have access to this views viewmodel in the addSongCommand.Execute() method, what logic would I put there?
public class AddSongCommand : CommandBase
{
private ViewModelBase _vm;
public AddSongCommand(ViewModelBase vm)
{
_vm = vm;
}
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
// what goes here?
}
}
<ListBox ItemsSource="{Binding SongList}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=.}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal" Visibility="{Binding IsAdding, Converter={booltovisflipconverter}}">
<Button Content="Add" Command="{Binding AddSongCommand}" />
<Button Content="Remove"/>
</StackPanel>
<StackPanel Orientation="Vertical" Visibility="{Binding IsAdding, Converter={booltovisconverter}}">
<TextBox Text="{Binding Song}"/>
<Button Content="Submit" Command="{Binding SubmitSongCommand}" />
</StackPanel>
Note: You will need to write the converter to flip the visibility, or use a datatrigger.
public class ViewModel : ViewModelBase
{
public ObservableCollection<string> SongList {get;set;} = new ObservableCollection<string>();
public bool IsAdding
{
get { ... }
set { notifychanged }
}
public string Song
{
get { ... }
set { notifychanged }
}
// called from add song command
public void EnableAdding()
{
IsAdding = true;
}
// called from submit command
public void SubmitSong()
{
SongList.Add(Song);
IsAdding = false;
}
}
public class SubmitSongCommand : CommandBase
{
private ViewModel _vm;
public SubmitSongCommand(ViewModel vm)
{
_vm = vm;
}
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
_vm.SubmitSong();
}
}
public class AddSongCommand : CommandBase
{
private ViewModel _vm;
public AddSongCommand(ViewModel vm)
{
_vm = vm;
}
public override bool CanExecute(object parameter) => true;
public override void Execute(object parameter)
{
_vm.EnableAdding();
}
}
The above uses specific types for each command. So many commands = many types.
You could implement a basic command using delegates instead.
public class SimpleCommand : ICommand
{
public Action<object> ExecuteAction {get;set;}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
}
public class ViewModel
{
public ViewModel()
{
AddSongCommand = new SimpleCommand()
{
ExecuteAction = (x) => { AddSong(); }
};
SubmitSongCommand = new SimpleCommand()
{
ExecuteAction = (x) => { SubmitSong(); }
};
}
public ICommand AddSongCommand { get; }
public ICommand SubmitSongCommand { get; }
public void AddSong()
{
// add song to list
}
public void SubmitSong()
{
// submit song
}
}
Im trying to change 2 textblocks with data binding. The propertyChanged is always null, so the ui wont update.
This is my model code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MovieApp.Models
{
public class MovieModel : INotifyPropertyChanged
{
string original_title, overview;
public event PropertyChangedEventHandler PropertyChanged;
public string Original_Title {
get
{
return original_title;
}
set
{
original_title = value;
onPropertyChanged(nameof(Original_Title));
}
}
public string Overview
{
get
{
return overview;
}
set
{
overview = value;
onPropertyChanged(nameof(Overview));
}
}
protected void onPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The mainview.xaml.cs:
using MovieApp.API;
using MovieApp.Models;
using MovieApp.Processor;
using System.Windows;
namespace MovieApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
//private readonly MovieModel movieModel = new MovieModel();
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
// DataContext = movieModel;
}
private async void previousImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 484718;
await MovieProcessor.LoadMovie(id);
}
private async void nextImageButton_Click(object sender, RoutedEventArgs e)
{
int id = 527774;
await MovieProcessor.LoadMovie(id);
}
}
}
and the maindwindow.xaml:
<Window x:Class="MovieApp.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:MovieApp.Models"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MovieModel x:Key="movieModel" />
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Click="previousImageButton_Click">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Click="nextImageButton_Click">Next</Button>
</Grid>
</Window>
EDIT:
Added the movieprocessor code:
using MovieApp.API;
using MovieApp.Models;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace MovieApp.Processor
{
class MovieProcessor
{
public static async Task<MovieModel> LoadMovie(int id)
{
string url = $"movie/{id}?api_key=77e7d2ef687aedca2119680778f1d619&language=en-US";
using (HttpResponseMessage response = await ApiCaller.httpClient.GetAsync(url))
{
if (response.IsSuccessStatusCode)
{
MovieModel movie = await response.Content.ReadAsAsync<MovieModel>();
Console.WriteLine(movie.Original_Title);
Console.WriteLine(movie.Overview);
return movie;
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
}
}
}
I have no idea what could be wrong. I tried multiple things but nothing seemed to work for me. I tried adding datacontext but that didnt work either. I let it commented in my code so anyone can see it.
If your MovieProcess class sets the value of MovieModel.Original_Title and MovieModel.Overview property then you have to ensure that MovieProcess is accessing the same instance of MovieModel as your view (xaml).
Instead of using StaticResource movieModel assing DataContext in code behind.
private readonly MovieModel movieModel = new MovieModel();
public MovieProcessor MovieProcessor { get; set; }
public MainWindow()
{
InitializeComponent();
ApiCaller.InitializeClient();
DataContext = movieModel;
MovieProcessor = new MoviewProcessor(moviewModel);
}
Xaml
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Original_Title}" />
<TextBlock Text="{Binding Overview }" />
</StackPanel>
MovieProcessor class
public class MovieProcessor
{
private readonly MovieModel movieModel;
public MovieProcessor(MovieModel movieModel)
{
this.movieModel = movieModel;
}
public async Task LoadMovie(int id)
{
...
movieModel.Original_Title = <loaded_movie_title>;
movieModel.Overview = <loaded_movie_overview>;
...
}
}
I bind the command to the Buttons in the MovieProcessor to show data in Maindwindow with StaticResource movieModel, below is my code:
NotifyObject.cs
public class NotifyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChange(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MyCommand.cs
public class MyCommand : ICommand
{
private Func<object, bool> _canExecute;
private Action<object> _execute;
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public bool CanExecute(object parameter)
{
if (_canExecute == null) return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
if (_execute != null && CanExecute(parameter))
{
_execute(parameter);
}
}
public MyCommand(Action<object> execute) : this(execute, null)
{
}
public MyCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
}
MovieProcessor.cs
public class MovieProcessor:NotifyObject
{
private MovieModel vm;
public MovieModel VM
{
get { return vm; }
set
{
vm = value;
OnPropertyChange("VM");
}
}
public MovieModel LoadMovie(int id)
{
//....
}
private MyCommand _cmd1;
public MyCommand Cmd1
{
get
{
if (_cmd1 == null)
_cmd1 = new MyCommand(new Action<object>
(
o =>
{
int id = 484718;
LoadMovie(id);
}
));
return _cmd1;
}
}
private MyCommand _cmd2;
public MyCommand Cmd2
{
get
{
if (_cmd2 == null)
_cmd2 = new MyCommand(new Action<object>
(
o =>
{
int id = 527774;
LoadMovie(id);
}
));
return _cmd2;
}
}
}
MainWindow.xaml
<Window.Resources>
<local:MovieProcessor x:Key="movieModel" />
</Window.Resources>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button x:Name="previousImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd1}">Previous</Button>
<StackPanel Grid.Row="1">
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Original_Title}" ></TextBlock>
<TextBlock Text="{Binding Source={StaticResource movieModel}, Path=VM.Overview }"></TextBlock>
</StackPanel>
<Button Grid.Row="2" x:Name="nextImageButton" Padding="15" Margin="15" Command="{Binding Source={StaticResource movieModel}, Path=Cmd2}">Next</Button>
</Grid>
I am using Material Design in XAML Toolkit. I have the main window with drawer, which contains the list of user controls (app tabs). When I click on them - application tab switches between this controls. I want to add a button to the window, and when I click on it I want to switch between tabs too. You can see important parts of my code here:
<materialDesign:DialogHost Identifier="RootDialog" SnackbarMessageQueue="{Binding ElementName=MainSnackbar, Path=MessageQueue}">
<materialDesign:DrawerHost IsLeftDrawerOpen="{Binding ElementName=MenuToggleButton, Path=IsChecked}">
<materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel MinWidth="212">
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}"
DockPanel.Dock="Top"
HorizontalAlignment="Right" Margin="16"
IsChecked="{Binding ElementName=MenuToggleButton, Path=IsChecked, Mode=TwoWay}" />
<ListBox x:Name="DemoItemsListBox" Margin="0 16 0 16" SelectedIndex="0"
ItemsSource="{Binding DemoItems}"
PreviewMouseLeftButtonUp="UIElement_OnPreviewMouseLeftButtonUp">
<ListBox.ItemTemplate>
<DataTemplate DataType="helpers:DemoItem">
<TextBlock Text="{Binding Name}" Margin="32 0 32 0" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DockPanel>
</materialDesign:DrawerHost.LeftDrawerContent>
<DockPanel>
<materialDesign:ColorZone Padding="16" materialDesign:ShadowAssist.ShadowDepth="Depth2"
Mode="PrimaryDark" DockPanel.Dock="Top">
<DockPanel>
<ToggleButton Style="{StaticResource MaterialDesignHamburgerToggleButton}" IsChecked="False"
x:Name="MenuToggleButton"/>
<materialDesign:PopupBox x:Name="popupBox">
<TextBlock>Check me please</TextBlock>
</materialDesign:PopupBox>
<CheckBox x:Name="LizenzeCheckBox" DockPanel.Dock="Right" Style="{StaticResource MaterialDesignCheckBox}" Tag="False">
<CheckBox.IsChecked>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}">
<Binding.ValidationRules>
<helpers:IsCheckedValidationRule />
</Binding.ValidationRules>
</Binding>
</CheckBox.IsChecked>CheckBox text</CheckBox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="22">My App</TextBlock>
</DockPanel>
</materialDesign:ColorZone>
<Button x:Name="TheBUTTON" Click="Button_Click">Ckicc</Button>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="1"
HorizontalScrollBarVisibility="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.HorizontalScrollBarVisibilityRequirement}"
VerticalScrollBarVisibility="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.VerticalScrollBarVisibilityRequirement}"
Padding="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.MarginRequirement}">
<ContentControl Content="{Binding ElementName=DemoItemsListBox, Path=SelectedItem.Content}" />
</ScrollViewer></Grid>
This is my main window xaml code, as you can see, I bind ListBox Values to DemoItem[] array from viewModel. "TheButton" onclick event is the event which I want to use for tab switching.
My main window view model is:
public class MainWindowViewModel
{
public DemoItem[] DemoItems { get; }
public MainWindowViewModel(ISnackbarMessageQueue snackbarMessageQueue)
{
if (snackbarMessageQueue == null) throw new ArgumentNullException(nameof(snackbarMessageQueue));
DemoItems = new[]
{
new DemoItem("Tab1", new Tab1()),
new DemoItem("Tab2", new Tab2()),
new DemoItem("Tab3", new Tab3()),
};
}
}
The MainWindow.cs is:
public partial class MainWindow : Window
{
public static Snackbar Snackbar;
public MainWindow()
{
InitializeComponent();
Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
}).ContinueWith(t =>
{
MainSnackbar.MessageQueue.Enqueue("Welcome to my app");
}, TaskScheduler.FromCurrentSynchronizationContext());
DataContext = new MainWindowViewModel(MainSnackbar.MessageQueue);
}
private void UIElement_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
//until we had a StaysOpen glag to Drawer, this will help with scroll bars
var dependencyObject = Mouse.Captured as DependencyObject;
while (dependencyObject != null)
{
if (dependencyObject is ScrollBar) return;
dependencyObject = VisualTreeHelper.GetParent(dependencyObject);
}
MenuToggleButton.IsChecked = false;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//what to do here?
}
}
The DemoItem Class is:
public class DemoItem : INotifyPropertyChanged
{
private string _name;
private object _content;
private ScrollBarVisibility _horizontalScrollBarVisibilityRequirement;
private ScrollBarVisibility _verticalScrollBarVisibilityRequirement;
private Thickness _marginRequirement = new Thickness(16);
public DemoItem(string name, object content)
{
_name = name;
Content = content;
}
public string Name
{
get { return _name; }
set { this.MutateVerbose(ref _name, value, RaisePropertyChanged()); }
}
public object Content
{
get { return _content; }
set { this.MutateVerbose(ref _content, value, RaisePropertyChanged()); }
}
public ScrollBarVisibility HorizontalScrollBarVisibilityRequirement
{
get { return _horizontalScrollBarVisibilityRequirement; }
set { this.MutateVerbose(ref _horizontalScrollBarVisibilityRequirement, value, RaisePropertyChanged()); }
}
public ScrollBarVisibility VerticalScrollBarVisibilityRequirement
{
get { return _verticalScrollBarVisibilityRequirement; }
set { this.MutateVerbose(ref _verticalScrollBarVisibilityRequirement, value, RaisePropertyChanged()); }
}
public Thickness MarginRequirement
{
get { return _marginRequirement; }
set { this.MutateVerbose(ref _marginRequirement, value, RaisePropertyChanged()); }
}
public event PropertyChangedEventHandler PropertyChanged;
private Action<PropertyChangedEventArgs> RaisePropertyChanged()
{
return args => PropertyChanged?.Invoke(this, args);
}
}
My MutateVerbose function looks like:
public static void MutateVerbose<TField>(this INotifyPropertyChanged instance, ref TField field, TField newValue, Action<PropertyChangedEventArgs> raise, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<TField>.Default.Equals(field, newValue)) return;
field = newValue;
raise?.Invoke(new PropertyChangedEventArgs(propertyName));
}
I don't know how to switch tabs with button click in this situation. Help me, please!
I have a MainWindow where I navigate between UserControls by clicking on a menu and it works fine.
I am using this following pattern:
https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/
In one of those usercontrol there is a button. By clicking on this button I want to navigate to another usercontrol.
How do I do that?
MainView
<UserControl.Resources>
<DataTemplate DataType="{x:Type cvm:FirstViewModel}">
<cv:FirstView/>
</DataTemplate>
<DataTemplate DataType="{x:Type cvm:SecondViewModel}">
<cv:SecondView/>
</DataTemplate>
<cvm:MainViewModel x:Key="main"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource main}}">
<Border Grid.Row="0">
<Menu Height="58">
<ItemsControl ItemsSource="{Binding PageViewModels}" Width="289" Height="58">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink Command="{Binding ChangePageCommand, Mode=OneWay, Source={StaticResource main}}" CommandParameter="{Binding}" TextDecorations="{x:Null}">
<InlineUIContainer>
<TextBlock Text="{Binding Name}"/>
</InlineUIContainer>
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Menu>
</Border>
<Border Grid.Row="1" >
<ContentControl Content="{Binding CurrentUserControl}"/>
</Border>
</Grid>
MainViewModel
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
}
#region Fields
private List<IUserContentViewModel> _pageViewModels;
public List<IUserContentViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IUserContentViewModel>();
return _pageViewModels;
}
}
private IUserContentViewModel _currentUserControl;
public IUserContentViewModel CurrentUserControl
{
get { return _currentUserControl; }
set
{
if (value != _currentUserControl)
{
_currentUserControl = value;
OnPropertyChanged("CurrentUserControl");
}
}
}
#region Methods
private void ChangeViewModel(IUserContentViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentUserControl = PageViewModels
.FirstOrDefault(vm => vm == viewModel);
}
#endregion
private ICommand _changePageCommand;
#endregion
public ICommand ChangePageCommand
{
get
{
if (_changePageCommand == null)
{
_changePageCommand = new RelayCommand(
p => ChangeViewModel((IUserContentViewModel)p),
p => p is IUserContentViewModel);
}
return _changePageCommand;
}
}
}
SecondView
<Grid Background="Blue">
<Button /> <!-- Going to ThirdView?????????-->
</Grid>
You have to call the ChangePageCommand from your button:
<Button DataContext="{Binding Source={StaticResource main}}"
Command="{Binding ChangePageCommand"}
CommandParameter="{Binding PageViewModels[2]}">
I am assuming that you have your FirstViewModel stored at PageViewModels[0], and your SecondViewModel at PageViewModels[1].
You also have to create a ThirdView link to your ThirdViewModel, as your other two Views/ViewModels:
<UserControl.Resources>
...
<DataTemplate DataType="{x:Type cvm:ThirdViewModel}">
<cv:ThirdView/>
</DataTemplate>
</UserControl.Resources>
And just as an advice, you can set your UserControl DataContext at the start of your code, instead of using it in any UIElement(Button and Grid in your case), like this:
<UserControl.DataContext>
<cvm:MainViewModel />
</UserControl.DataContext>
EDIT>>>>
Forgot to say that you also have to add your ThirdViewModel to your PageViewModels collection:
PageViewModels.Add(new ThirdViewModel());
I finally have my solution.
I use Event Aggregator with Prism 6.
First I create a Singleton.
internal sealed class ApplicationService
{
private ApplicationService() { }
private static readonly ApplicationService _instance = new ApplicationService();
internal static ApplicationService Instance { get { return _instance; } }
private IEventAggregator _eventAggregator;
internal IEventAggregator EventAggregator
{
get
{
if (_eventAggregator == null)
_eventAggregator = new EventAggregator();
return _eventAggregator;
}
}
}
Then public class GoToThird : PubSubEvent<TEvent> { }
In MainViewModel I subscribe to the event and add my ThirdViewModel().
public class MainViewModel : ViewModelBase
{
protected readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
PageViewModels.Add(new FirstViewModel());
PageViewModels.Add(new SecondViewModel(ApplicationService.Instance.EventAggregator)));
PageViewModels.Add(new ThirdViewModel());
// Set starting page
CurrentUserControl = PageViewModels[0];
this._eventAggregator = eventAggregator;
}
private void GoToThird()
{
CurrentUserControl = PageViewModels[2];
}
}
At the end I publish the event in SecondViewModel()
public class SecondViewModel
{
protected readonly IEventAggregator _eventAggregator;
public SecondViewModel(IEventAggregator eventAggregator)
{
this._eventAggregator = eventAggregator;
}
private void Go()
{
_eventAggregator.GetEvent<GoToThird>().Publish();
}
private ICommand goToThirdCommand;
public ICommand GoToThirdCommand
{
get
{
return goToThirdCommand ?? (goToThirdCommand = new RelayCommand(p => this.Go(), p => this.CanGo()));
}
}
private bool CanGo()
{
return true;
}
}
Big Thanks to Rachel and Kirenenko
I have a problem and I'm not sure what it is. I have a class within a class that has a value that needs to be bound to a control, in this case visibility. The code is changing the value correctly but the output does not change (i.e collapse the control)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<StackPanel Orientation="Vertical">
<Button x:Name="buttonOne" Content="Show Hide" Width="Auto" Click="buttonOne_Click"/>
<ListBox x:Name="aListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="blockOne" Grid.Column="0" Text="Raw "/>
<TextBlock x:Name="blockTwo" Grid.Column="1" Text="{Binding aValue}" Visibility="{Binding Path=visControl.VisibleState, BindsDirectlyToSource=True}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
public partial class MainPage : PhoneApplicationPage
{
private List<myClass> listOfClasses = new List<myClass>();
// Constructor
public MainPage()
{
myClass classA = new myClass("one");
myClass classB = new myClass("two");
myClass classC = new myClass("three");
listOfClasses.Add(classA);
listOfClasses.Add(classB);
listOfClasses.Add(classC);
InitializeComponent();
aListBox.ItemsSource = listOfClasses;
}
private void buttonOne_Click(object sender, RoutedEventArgs e)
{
foreach (myClass cl in listOfClasses)
if (cl.SwitchVisible)
cl.SwitchVisible = false;
else
cl.SwitchVisible = true;
}
}
public class myClass
{
private string _aValue;
private bool _switchVisible;
public bool SwitchVisible { get { return _switchVisible; } set { _switchVisible = value; visControl.changeVisibility(_switchVisible); } }
public string aValue { get { return _aValue; } }
public controlProperties visControl;
public myClass(string invalue)
{
visControl = new controlProperties();
visControl.VisibleState = Visibility.Visible;
_aValue = invalue;
}
}
public class controlProperties
{
private Visibility _visibility;
public Visibility VisibleState { get { return _visibility; } set { _visibility = value; } }
public void changeVisibility(bool isVisible)
{
if (isVisible)
_visibility = Visibility.Visible;
else
_visibility = Visibility.Collapsed;
}
}
Any ideas if this is a pathing issue or a binding problem?
If you want the control to be automatically updated when you change the value of the property, your class must implement the INotifyPropertyChanged interface.
For instance:
public class controlProperties : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Visibility _visibility;
public Visibility VisibleState
{
get
{
return _visibility;
}
set
{
_visibility = value;
this.NotifyPropertyChanged("VisibleState");
}
}
public void changeVisibility(bool isVisible)
{
if (isVisible)
this.VisibleState = Visibility.Visible;
else
this.VisibleState = Visibility.Collapsed;
}
private void NotifyPropertyChanged(string propertyName)
{
var eventHandler = this.PropertyChanged;
if (eventHandler != null)
{
eventHandler(sender, new PropertyChangedEventArgs(propertyName));
}
}
}