I have 2 Pages with almost identical code behind (.xaml.cs file). They have different layout though (.xaml files are different). In Code-behind file, the only difference is the type of the variable. All other procedures/functions are exactly the same.
For example:
Page1:
public sealed partial class Page1 : Page
{
public List<CarVersion1> cars = new List<CarVersion1>();
public CarVersion1 currentCar;
...
private UpdatePrice(int p) {
currentCar.Price = p;
}
}
Page2:
public sealed partial class Page2 : Page
{
public List<CarVersion2> cars = new List<CarVersion2>();
public CarVersion2 currentCar;
...
private UpdatePrice(int p) {
currentCar.Price = p;
}
}
Is there anyway to use just 1 code-behind file instead of duplicating it?
I guess you need to create a ViewModel and put all your common logics there. Something like this:
CarVersions.cs
namespace Pages;
public class CarVersion
{
public string Version { get; set; } = string.Empty;
}
public class CarVersion1 : CarVersion
{
public CarVersion1()
{
Version = nameof(CarVersion1);
}
}
public class CarVersion2 : CarVersion
{
public CarVersion2()
{
Version = nameof(CarVersion2);
}
}
ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
[ObservableObject]
public partial class ViewModel<T> where T : CarVersion, new()
{
[ObservableProperty]
private T carVersion = new();
}
Page1.xaml.cs
using Microsoft.UI.Xaml.Controls;
namespace Pages;
public sealed partial class Page1 : Page
{
public Page1()
{
this.InitializeComponent();
}
public ViewModel<CarVersion1> ViewModel { get; } = new();
}
Page2.xaml.cs
using Microsoft.UI.Xaml.Controls;
namespace Pages;
public sealed partial class Page2 : Page
{
public Page2()
{
this.InitializeComponent();
}
public ViewModel<CarVersion2> ViewModel { get; } = new();
}
Page1.xaml
<Page
x:Class="Pages.Page1"
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:local="using:Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<TextBlock Text="{x:Bind ViewModel.CarVersion, Mode=OneWay}" />
</Grid>
</Page>
Page2.xaml
<Page
x:Class="Pages.Page2"
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:local="using:Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid>
<TextBlock Text="{x:Bind ViewModel.CarVersion, Mode=OneWay}" />
</Grid>
</Page>
MainWindow.xaml
<Window
x:Class="Pages.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:local="using:Pages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid ColumnDefinitions="*,*">
<local:Page1 Grid.Column="0" />
<local:Page2 Grid.Column="1" />
</Grid>
</Window>
You can create a BaseViewModel for both of the Pages. Then inherit it.
you can refer to this sample example for reference.
Related
So I have created a button and I link it to an ICommand NavigateToVM but it does not hit the execute function when I try to click the button. I am I doing something wrong. I'll post the code that is relevant. Thanks in advance.
The Profile button is the button I am trying to get to work. It is just a standard Icommand.
{
public class NavigationBarVM : BaseViewModel
{
public ICommand NavigateToVMCmd { get; set; }
public NavigationBarVM()
{
}
public NavigationBarVM( NavigationStore _navigationStore)
{
NavigateToVMCmd = new NavigateToProfileCommand(this, _navigationStore);
}
}
}
namespace WpfNotes.Commands
{
public class NavigateToProfileCommand : CommandBase
{
private readonly NavigationBarVM navBarVM;
private readonly NavigationStore _navigationStore;
public NavigateToProfileCommand(NavigationBarVM VM, NavigationStore navigationStore)
{
navBarVM = VM;
_navigationStore = navigationStore;
}
public override void Execute(object parameter)
{
_navigationStore.CurrentViewModel = new ProfileVM();
Debug.WriteLine("Stuff");
}
}
}
<UserControl x:Class="WpfNotes.View.NavigationBar"
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:local="clr-namespace:WpfNotes.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:VM ="clr-namespace:WpfNotes.ViewModel">
<UserControl.Resources>
<VM:NavigationBarVM x:Key="vm"/>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="2">
<UniformGrid Columns="4" Height="40">
<Button BorderThickness="0" Content="Profile" Command="{Binding Source={StaticResource vm}, Path = NavigateToVMCmd }"></Button>
</UniformGrid>
</Border>
</UserControl>
You should instantiate the NavigationBarVM programmatically and inject it with a NavigationStore:
public partial class NavigationBar : UserControl
{
public MainWindow()
{
InitializeComponent();
DataContext = new NavigationBarVM(new NavigationStore());
}
}
You can then bind directly to the command of the view model:
<Button BorderThickness="0" Content="Profile"
Command="{Binding NavigateToVMCmd}" />
The following initializes the NavigationBarVM using the default constructor which is pretty useless and should be removed in your case as it doesn't initialize the command:
<VM:NavigationBarVM x:Key="vm"/>
I'm pretty new in MvvmCross but I'm working with mvvm for awhile. I know how to compose usercontrols with nested usercontrols. Now with mvvmcross I got stucked to show two or more usercontrols in another usercontrol. I don't use any other framework than MvvmCross.
My Rootview looks like this:
`<views:MvxWpfView
x:Class="MvvmCrossTest.Wpf.Views.RootView"
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<DockPanel>
<TextBlock Text="Root" DockPanel.Dock="Top"/>
<ContentControl x:Name="MainMenuVM" Content="{Binding MainMenuVM}" DockPanel.Dock="Top" />
</DockPanel>
</views:MvxWpfView>`
The corresponding ViewModel looks like this:
using MvvmCross.Commands;
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvvmCrossTest.Core.ViewModels
{
public class RootViewModel: MvxNavigationViewModel
{
private readonly IMvxViewModelLoader _mvxViewModelLoader;
public RootViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService, IMvxViewModelLoader mvxViewModelLoader) : base(logProvider, navigationService)
{
_mvxViewModelLoader = mvxViewModelLoader;
ShowMainMenu();
}
private MainMenuViewModel _mainMenuVM;
public MainMenuViewModel MainMenuVM
{
get { return _mainMenuVM; }
set
{
SetProperty(ref _mainMenuVM, value);
RaisePropertyChanged(() => MainMenuVM);
}
}
public MvxCommand ShowMainMenuCommand { get; set; }
public void ShowMainMenu()
{
MainMenuVM = (MainMenuViewModel)_mvxViewModelLoader.LoadViewModel(MvxViewModelRequest.GetDefaultRequest(typeof(MainMenuViewModel)), null, null);
}
}
}
The simplified View I want to show in the contentcontrol looks like this:
<views:MvxWpfView
x:Class="MvvmCrossTest.Wpf.Views.MainMenuView"
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
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"
mc:Ignorable="d"
Background="Aqua"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
</Grid>
</views:MvxWpfView>
And also the corresponding ViewModel.
using MvvmCross.Logging;
using MvvmCross.Navigation;
using MvvmCross.ViewModels;
namespace MvvmCrossTest.Core.ViewModels
{
public class MainMenuViewModel : MvxNavigationViewModel
{
public MainMenuViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
}
}
}
Instead of the View/ViewModel I see a text that says "MvvmCrossTest.Core.ViewModels.MainMenuViewModel".
I don't want to use Xamarin!
I created a project where I show you some basics.
simple
You can find the samples here.
the RootView;
<views:MvxWpfView xmlns:local="clr-namespace:SomeProject.Views" ...>
<Grid>
<Label Content="Hello from RootView" />
<local:NestedView />
</Grid>
</views:MvxWpfView>
the NestedView;
<views:MvxWpfView ...>
<Grid>
<Label Content="Hello from NestedView" />
</Grid>
</views:MvxWpfView>
extended (custom presenter)
With this custom wpf presenter you can easily navigate to views while specified themn in a container. With that, you can close and open views from the view model while using the mvvmcross navigation service.
I copied this from another project. You can find the whole implementation in my sample project here.
the RootView;
<views:MvxWpfView ...>
<Grid>
<Label Content="Hello" />
<ItemsControl region:MvxContainer.Id="RootViewRegion"/>
</Grid>
</views:MvxWpfView>
the RootView code behind;
[MvxContentPagePresentation(WrapInNavigationPage = true, NoHistory = false)]
public partial class RootView: MvxWpfView<RootViewModel>
{
public RootView()
{
InitializeComponent();
}
}
the RootViewModel;
public class RootViewModel: MvxNavigationViewModel
{
private readonly IMvxNavigationService _navigationService;
public IMvxAsyncCommand ShowNestedViewModelCommand { get; protected set; }
public RootViewModel(IMvxLogProvider logProvider, IMvxNavigationService navigationService) : base(logProvider, navigationService)
{
this._navigationService = navigationService;
// navigate to nested view
this.ShowNestedViewModelCommand = new MvxAsyncCommand(() => this._navigationService.Navigate<NestedViewModel>());
ShowNestedViewModelCommand.Execute();
}
}
the NestedView code behind;
[MvxWpfPresenter("RootViewRegion", mvxViewPosition.NewOrExsist)]
public partial class NestedView : MvxWpfView<NestedViewModel>
{
public NestedView()
{
InitializeComponent();
}
}
good to know
when you have problems with navigation and view model connection use this code behind. It loads the viewmodel for the specific view;
[MvxContentPagePresentation(WrapInNavigationPage = true, NoHistory = false)]
public partial class SomeView : MvxWpfView<ViewModels.SomeViewModel>
{
public SomeView()
{
InitializeComponent();
if (!(ViewModel is ViewModels.SomeViewModel))
{
if (Mvx.IoCProvider.TryResolve<ViewModels.SomeViewModel>(out var someViewModel))
{
ViewModel = someViewModel;
return;
}
var _viewModelLoader = Mvx.IoCProvider.Resolve<IMvxViewModelLoader>();
var request = new MvxViewModelInstanceRequest(typeof(ViewModels.SomeViewModel));
request.ViewModelInstance = _viewModelLoader.LoadViewModel(request, null);
ViewModel = request.ViewModelInstance as ViewModels.SomeViewModel;
Mvx.IoCProvider.RegisterSingleton<ViewModels.SomeViewModel>(ViewModel);
}
}
}
it seems that MvvmCross is wiring up Views and ViewModels automatically
I know nothing about MvvmCross, but for "automatically" to work there should be an agreement and rules, e.g. attributes or matching names (xViewModel <-> xView). Currently there is nothing I can see what connects MainMenuViewModel with views:MvxWpfView.
Anyway what you see
I see a text that says "MvvmCrossTest.Core.ViewModels.MainMenuViewModel"
is the view trying to visualize non-control as text due to missing data template.
It's easy to fix by just adding data template
<DataTemplate DataType="{x:Type local:MainMenuViewModel}">
<views:MvxWpfView />
</DataTemplate>
somewhere (e.g. into views:MvxWpfView.Resources) and the view will be displayed.
I have 2 classes (InnerModel and OuterModel). OuterModel contains 2 InnerModel instances. I would like to create UserControls for them (InnerUserControl and OuterUserControl). OuterUserControl contains 2 InnerUserControls. But I can't figure out how to make binding works in this case.
Below is the complete code of what I try to do.
Please advise how to fix it to get the same result as on a pic at the end.
Thanks in advance!
MainWindow.xaml.cs
<Window x:Class="NestedUserControl.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:NestedUserControl"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="450">
<Grid>
<local:OuterUserControl x:Name="test"/>
</Grid>
MainWindow.xaml
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var model = new OuterModel("TEST1", "TEST2");
test.DataContext = model;
}
}
InnerModel.cs
public class InnerModel : INotifyPropertyChanged
{
public String Data
{
get { return data; }
set { data = value; }
}
private string data;
public event PropertyChangedEventHandler PropertyChanged;
public InnerModel(string _data) => data = _data;
public void OnPropertyChanged([CallerMemberName]string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
InnerUserControl.xaml
<UserControl x:Class="NestedUserControl.InnerUserControl"
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:local="clr-namespace:NestedUserControl"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="450">
<Grid>
<TextBlock Text="{Binding Path=Data}"/>
</Grid>
InnerUserControl.xaml.cs
public partial class InnerUserControl : UserControl
{
public InnerUserControl()
{
InitializeComponent();
}
}
OuterModel.cs
public class OuterModel : INotifyPropertyChanged
{
public InnerModel model1;
public InnerModel model2;
public event PropertyChangedEventHandler PropertyChanged;
public OuterModel(string data1, string data2)
{
model1 = new InnerModel(data1);
model2 = new InnerModel(data2);
}
public void OnPropertyChanged([CallerMemberName]string prop = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
}
OuterUserControl.xaml
<UserControl x:Class="NestedUserControl.OuterUserControl"
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:local="clr-namespace:NestedUserControl"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="450">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<local:InnerUserControl Grid.Row="0" x:Name="inner1" DataContext="model1"/>
<local:InnerUserControl Grid.Row="1" x:Name="inner2" DataContext="model2"/>
</Grid>
OuerUserControl.xaml.cs
public partial class OuterUserControl : UserControl
{
public OuterUserControl()
{
InitializeComponent();
}
}
Working binding MainWindow.xaml.cs
Welcome to SO!
Two problems here, first of all your inner models need to be properties, so change their declarations to this:
public InnerModel model1 {get; set;}
public InnerModel model2 {get; set;}
Second problem is with your bindings, you need to do this instead:
<local:InnerUserControl Grid.Row="0" x:Name="inner1" DataContext="{Binding model1}"/>
<local:InnerUserControl Grid.Row="1" x:Name="inner2" DataContext="{Binding model2}"/>
Im trying to understand how to use a viewmodel to bind data to a wpf window but cannot find any simple examples or explanations on how that is achieved.
This is what I have so far:
ViewModel.cs
public class ViewModel
{
public string Info = "Infoo";
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
MainWindow.xaml
<Window x:Class="PeopleApp.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:PeopleApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="212,137,0,0" TextWrapping="Wrap" Text="{Binding Info}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
Why does the textbox not show "Infoo"? What am I missing?
Edit:
I've changed the field in the ViewModel to a property, but how do I connect the ViewModel to actual data?
Mistakes in your code:
You need to have property on the VM not the public field.
you need to set the value for the property of VM instance before assigning it to the DataContext of MainWindow
This should work.
MainWindow.xaml
<Window x:Class="PeopleApp.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:PeopleApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="212,137,0,0" TextWrapping="Wrap" Text="{Binding Info}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel() { Info = "Infoo" };
}
}
ViewModel.cs
public class ViewModel
{
public string Info { get; set; }
}
Your View(Window) and ViewModel(class) are various parts of application. You should use DataContext property to interact between View and ViewModel and create properties, not fields like you've done in your case. For example:
View:
<Grid>
<TextBlock Text="{Binding DisplayTime}" />
</Grid>
ViewModel:
public class MyViewModel
{
private string displayTime=DateTime.Now.ToString;
public string DisplayTime
{
get { return displayTime; }
set { displayTime = value; }
}
}
There are many approaches to set DataContext:
The first approach. In view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
The second approach. You should override OnStartUp() method of App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
ProductViewModel context = new ProductViewModel();
app.DataContext = context;
app.Show();
}
}
The third approach. In constructor of Windows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext=new MainWindowViewModel();
}
}
The fourth approach. You can set DataContext through DependencyInjection by UnityContainer. But DependencyInjection, Prism and UnityContainer are other questions and goes from this scope of the question. Just for example:
protected override void RegisterTypes()
{
unityContainer.RegisterType<object, ItemControl>("ModuleAUpper");
unityContainer.RegisterType<IViewModelItemControl, ViewModelItemControl>();
unityContainer.RegisterTypeForNavigation<ItemControl>();
}
I have been trying to bind listpickerflyout to some data and it doesn't seem to display the data when I debug.
Here is the XAML Code:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App6"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ViewModel="using:App6.ViewModel"
xmlns:Model="using:App6.Model"
x:Class="App6.MainPage"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<ViewModel:MainViewModel/>
</Page.DataContext>
<Grid>
<Button Margin="10,0,0,583" Width="313">
<Button.Flyout>
<ListPickerFlyout ItemsSource="{Binding Language.Name}"/>
</Button.Flyout>
<Button.DataContext>
<Model:Language/>
</Button.DataContext>
</Button>
</Grid>
And the other codes MVVM model:
namespace App6.Model
{
public class Language
{
public string Name { get; set; }
public int id { get; set; }
}
}
viewmodel:
namespace App6.ViewModel
{
public class MainViewModel
{
public Language Language { get; set; }
public MainViewModel()
{
Language = new Language
{
Name = "English",
id = 1
};
}
}
}
You are assigning an object of type "Language" to the datacontext of the button. Therefore the Binding Path is wrong as "Language" does not have a property "Language".
You should create an instance of your viewmodel and assign it to the datacontext of the window. No assignment on the button.