WPF Caliburn.Micro and DXTabControl with UserControl not working - c#

I'm trying to use a TabControl to switch between UserControls.
I could just set the content of the tabs to the usercontrols with XAML but then it will only be bound to the view and not the viewmodel.
My VM is a Caliburn.Micro Conductor and it calls ActivateItem whenever the user switches tabs. It worked fine when I only have one usercontrol, but when I created another one the first one will not load the view.
Here's some of the code I'm using:
ShellView:
<dx:ThemedWindow x:Class="PSCServiceManager.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="Service Manager" WindowState="Maximized"
Height="525" Width="720">
<Grid>
<dx:DXTabControl>
<dx:DXTabItem Header="Master Teknisi">
<ContentControl x:Name="LoadMasterTechnicianView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
<dx:DXTabItem Header="Servisan">
<ContentControl x:Name="LoadServicesView" cal:View.Model="{Binding ActiveItem}" />
</dx:DXTabItem>
</dx:DXTabControl>
</Grid>
ShellViewModel:
using Caliburn.Micro;
namespace PSCServiceManager.ViewModels
{
public class ShellViewModel : Conductor<IScreen>
{
private MasterTechnicianViewModel masterTechnicianViewModel;
private ServicesViewModel servicesViewModel;
public ShellViewModel()
{
LoadMasterTechnicianView();
}
public void LoadMasterTechnicianView()
{
ActivateItem(masterTechnicianViewModel);
}
public void LoadServicesView()
{
ActivateItem(servicesViewModel);
}
}
}

An easier/alternative way to implement this would be to create a collection of User Controls you would like to bind to the Tab Control. For example,
public interface ITabUserControl
{
string DisplayName { get; set; }
}
public class MasterTechnicianViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Master Technician";
}
public class ServicesViewModel : ITabUserControl
{
public string DisplayName { get; set; } = "Services";
}
Now in your ShellViewModel, you could create a Collection of ITabUserControl
public List<ITabUserControl> UserControls { get; set; }
public ShellViewModel()
{
UserControls = new List<ITabUserControl>();
UserControls.Add(new MasterTechnicianViewModel());
UserControls.Add(new ServicesViewModel());
}
And bind your TabControl as
<dx:DXTabControl x:Name="UserControls"/>
Now you can switch between the controls without any issues, without Activating it explicitly.

Related

x:Bind in child user controls

In WPF I use the following pattern sometimes:
public class InnerViewModel {
public int I {get ;set;}
}
public class OuterViewModel {
public InnerViewModel Inner { get; } = new InnerViewModel();
}
Outer view:
<UserControl x:Class="OuterView" ...>
<local:InnerView DataContext="{Binding Inner}"/>
</UserControl>
Inner view:
<UserControl x:Class="InnerView" ...>
<TextBox Value="{Binding I}"/>
</UserControl>
In WinUI 3 I can use x:Bind instead of Binding. That comes often with a ViewModel property in the code behind file of the view:
public sealed partial class OuterView : UserControl {
public OuterViewModel ViewModel {
get;
}
public OuterView() {
this.InitializeComponent();
this.ViewModel = App.GetRequiredService<OuterViewModel>();
}
}
and
<UserControl x:Class="OuterView" ...>
<local:InnerView DataContext="{x:Bind Inner}"/>
</UserControl>
But for InnerView I cannot use x:Bind, since it does not work with DataContext.
Is there any way to initialized something like a ViewModel-property of the InnerView in a similar way as the DataContext-binding? Or is it just not possible to apply this pattern to the x:Bind-approach?
I figured out: x:Bind works also with non-dependency properties, so I can do the following:
public sealed partial class InnerView : UserControl {
public InnerViewModel? ViewModel {
get; set;
}
public InnerView() {
this.InitializeComponent();
}
}
<UserControl x:Class="OuterView" ...>
<local:InnerView ViewModel="{x:Bind Inner}"/>
</UserControl>
and
<UserControl x:Class="InnerView" ...>
<TextBox Value="{x:Bind ViewModel.I}"/>
</UserControl>
InnerView.ViewModel is not initialized in the constructor, so it needs a setter, but it seems to work.

How do I data bind in a new popup window?

I'm a beginner in C# WPF programming. I'm trying to pop up a new window(dialog) to which some information is passed. The new window is to show the information on itself.
I pass some variables through the constructor of the popup window(OrderEntry), and I want the popup window to show the variables within its textblock control. Very simple task. But it's not working. I guess I did something wrong regarding the data-binding in the popup window's XAML code, but I can't figure it out. What did I do wrong? Please help.
The following is the MainWindow code-behind:
public partial class MainWindow : Window
{
public List<Order> Orders { get; set; }
public MainWindow()
{
InitializeComponent();
DataAccess da = new DataAccess();
Orders = da.GetOrders();
OrdersGrid.ItemsSource = Orders;
}
private void OrdersGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Order item = (Order)OrdersGrid.SelectedItem;
OrderEntry dialog = new OrderEntry(item);
dialog.Show();
}
}
The next is the XAML code for OrderEntry.xaml:
<StackPanel>
<TextBlock Text="{Binding Path=_CustomerName}" />
</StackPanel>
The following is the class for the new popup window:
public partial class OrderEntry : Window
{
public Order Order { get; set; }
public string _CustomerName { get; set; }
public OrderEntry(Order order)
{
InitializeComponent();
Order = order;
_CustomerName = order.CustomerName;
}
}
Here you are trying to use the code behind as the DataContext. In most of the cases we do binding using the ViewModel class. In your case, you can follow the following.
First you need to assign a name to your view OrderEntry in OrderEntry.xaml
<Window x:Class="OrderEntry"
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"
x:Name="OrderEntry1"
Title="CodeBehindDataContext" Height="450" Width="800">
Then assign the binding as follows.
<StackPanel>
<TextBlock Text="{Binding ElementName=OrderEntry1, Path=_CustomerName}" />
</StackPanel>
Now change your OrderEntry constructor like below.
public OrderEntry(Order order)
{
Order = order;
_CustomerName = order.CustomerName;
InitializeComponent();
}
Set the datacontext of order window to itself.
Something like this
public partial class OrderEntry : Window
{
public Order Order { get; set; }
public string _CustomerName { get; set; }
public OrderEntry(Order order)
{
InitializeComponent();
DataContext = this;//datacontext to itself
Order = order;
_CustomerName = order.CustomerName;
}
}

how to bind listView ItemsSource to base type but pass in specific type

I have a custom User Control that has a ListView.
I also have a simple object hierarchy where 'Group' 'Questions' and so on all inherit from BaseEntity.
I am trying to create a user control that can bind to any BaseEntity type and show its Name and Id(base type properties). this way I can re-use it throughout the application for whatever ObservableCollection basic details I want to display in the control
it works when I bind an ObservableCollection of BaseEntity but not a specific type. unless I change the dependency properties to be that specific type - but that defeats the whole point.
How can I cast down to the base type on binding in xaml?
so I could bind with collections of:
ObservableCollection<Group>//inherits from BaseEntity
or
ObservableCollection<OtherType>//inherits from BaseEntity
or
ObservableCollection<BaseEntity> //its a BaseEntity
ideally, something like {Binding Path=(BaseEntity)OwnedGroups} - but it's not being that straight forward
==================the Control.xaml.cs==============
public partial class InOrOutControl : UserControl
{
public ObservableCollection<BaseEntity> EntitiesOwned
{
get { return (ObservableCollection<BaseEntity>)GetValue(EntitiesOwnedProperty); }
set { SetValue(EntitiesOwnedProperty, value); }
}
public static readonly DependencyProperty EntitiesOwnedProperty =
DependencyProperty.Register(nameof(EntitiesOwned), typeof(ObservableCollection<BaseEntity>), typeof(InOrOutControl), new PropertyMetadata(new ObservableCollection<BaseEntity>(), SetOwnedItemsSource));
private static void SetOwnedItemsSource(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
InOrOutControl controll = d as InOrOutControl;
if (controll != null)
{
controll.OutItemsLV.ItemsSource = e.NewValue as ObservableCollection<BaseEntity>;
}
}
public InOrOutControl()
{
InitializeComponent();
}
}
=================Control xaml=================
<UserControl x:Class="HonorsProject.View.CustomControlls.InOrOutControl"
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:HonorsProject.View.CustomControlls"
xmlns:appCore="clr-namespace:HonorsProject.Model.Core;assembly=HonorsProject.Model"
mc:Ignorable="d"
Name="InOrOutCtrl"
d:DesignHeight="450" d:DesignWidth="800">
<ListView Name="OutItemsLV"
ItemsSource="{Binding EntitiesOwned, ElementName=InOrOutCtrl}"
Grid.Row="1"
Height="100"
Grid.Column="0">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
==============the control in the Page===============
<uc:InOrOutControl x:Name="InOrOutControl"
EntitiesOwned="{Binding Path=OwnedGroups,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
===============Base Type========
public class BaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
=============Specific type========
public class Group : BaseEntity
{
public virtual List<Student> Students { get; set; }
public int SomeSpecificProperty {get;set;}
public Group()
{
Students = new List<Student>();
}
}

MVVM Dynamically add fields into View

I'm developing a WPF application using caliburn.micro MVVM framework..
In-order to develop a search screen, I need to dynamically load fields into the view, based on model properties.
Consider below view and view model:
SearchViewModel
SearchView
Let's assume T is a type of Product in below example.
public class SearchViewModel<T>
{
public T Item{get;set;}
}
public class Product
{
public int Id{get;set;}
public string Name{get;set;}
public string Description{get;set;}
}
I have a user control called SearchView.xaml with no contents on it.
Whenever View is loaded new fields should be added to the view and field should be bound to the properties.
According to above code example, there are 3 public properties in the Product class, therefore 3 TextBoxes should be added to the view dynamically. When user enters data in the text field, corresponding property should be updated.
Is this possible?
Can any experts help me to achieve this by providing some examples?
I would propose going about this differently. Instead of thinking about dynamically adding properties to a view / model, I would think about adding information about those properties to a list on the viewmodel. That list would then be bound to an ItemsControl with a template that looks like a TextBox.
So your view-model would have a property on it for the "thing" you want to examine. In the setter for this property, use reflection to enumerate the properties you are interested in, and add an instance of some kind of FieldInfo class (that you create) to the list of properties with the binding.
This has the benefit of keeping everything all MVVM compatible too, and there is no need to dynamically create controls with your own code.
The example below uses my own MVVM library (as a nuget package) rather than caliburn.micro, but it should be similar enough to follow the basic idea. The full source code of the example can be downloaded from this BitBucket repo.
As you can see in the included screenshots, the search fields are created dynamically on the view without any code in the view. Everything is done on the viewmodel. This also gives you easy access to the data that the user enters.
The view-model:
namespace DynamicViewExample
{
class MainWindowVm : ViewModel
{
public MainWindowVm()
{
Fields = new ObservableCollection<SearchFieldInfo>();
SearchableTypes = new ObservableCollection<Type>()
{
typeof(Models.User),
typeof(Models.Widget)
};
SearchType = SearchableTypes.First();
}
public ObservableCollection<Type> SearchableTypes { get; }
public ObservableCollection<SearchFieldInfo> Fields { get; }
private Type _searchType;
public Type SearchType
{
get { return _searchType; }
set
{
_searchType = value;
Fields.Clear();
foreach (PropertyInfo prop in _searchType.GetProperties())
{
var searchField = new SearchFieldInfo(prop.Name);
Fields.Add(searchField);
}
}
}
private ICommand _searchCommand;
public ICommand SearchCommand
{
get { return _searchCommand ?? (_searchCommand = new SimpleCommand((obj) =>
{
WindowManager.ShowMessage(String.Join(", ", Fields.Select(f => $"{f.Name}: {f.Value}")));
})); }
}
}
}
The SearchFieldInfo class:
namespace DynamicViewExample
{
public class SearchFieldInfo
{
public SearchFieldInfo(string name)
{
Name = name;
}
public string Name { get; }
public string Value { get; set; } = "";
}
}
The view:
<Window
x:Class="DynamicViewExample.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="clr-namespace:DynamicViewExample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
d:DataContext="{d:DesignInstance local:MainWindowVm}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ComboBox
Grid.Row="0"
ItemsSource="{Binding Path=SearchableTypes}"
SelectedItem="{Binding Path=SearchType}" />
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=Fields}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBox Width="300" Text="{Binding Path=Value}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Grid.Row="2" Command="{Binding Path=SearchCommand}">Search</Button>
</Grid>
</Window>
The model classes:
class User
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string PhoneNumber { get; set; }
public string Id { get; set; }
}
class Widget
{
public string ModelNumber { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
Here is a basic example of how you could generate a TextBox per public property of the T in the control using reflection.
SearchView.xaml:
<Window x:Class="WpfApplication4.SearchView"
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:WpfApplication4"
mc:Ignorable="d"
Title="SearchView" Height="300" Width="300">
<StackPanel x:Name="rootPanel">
</StackPanel>
</Window>
SearchView.xaml.cs:
public partial class SearchView : UserControl
{
public SearchView()
{
InitializeComponent();
DataContextChanged += SearchView_DataContextChanged;
DataContext = new SearchViewModel<Product>();
}
private void SearchView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
Type genericType = e.NewValue.GetType();
//check the DataContext was set to a SearchViewModel<T>
if (genericType.GetGenericTypeDefinition() == typeof(SearchViewModel<>))
{
//...and create a TextBox for each property of the type T
Type type = genericType.GetGenericArguments()[0];
var properties = type.GetProperties();
foreach(var property in properties)
{
TextBox textBox = new TextBox();
Binding binding = new Binding(property.Name);
if (!property.CanWrite)
binding.Mode = BindingMode.OneWay;
textBox.SetBinding(TextBox.TextProperty, binding);
rootPanel.Children.Add(textBox);
}
}
}
}
}
The other option will obviously be to create a "static" view for each type of T and define the TextBox elements in the XAML markup as usual.

How to bind a single object instance in WPF?

I am a WPF newcomer, and I've been searching for two days with no luck. I have a WPF window that has several text box controls, and a single object with some properties. This object is passed to the codebehind of my WPF window in it's constructor:
public partial class SettingsDialog : Window
{
public SettingsObject AppSettings
{
get;
set;
}
public SettingsDialog(SettingsObject settings)
{
this.AppSettings = settings;
InitializeComponent();
}
}
The SettingsObject looks something like this (simplified for clarity):
public class SettingsObject
{
public string Setting1 { get; set; }
public string Setting2 { get; set; }
public string Setting3 { get; set; }
public SettingsObject()
{
this.Setting1 = "ABC";
this.Setting2 = "DEF";
this.Setting3 = "GHI";
}
}
And my WPF window (simplified):
<Window x:Class="MyProgram.SettingsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding Source=AppSettings}">
<Grid>
<TextBox Name="Setting1Textbox" Text="{Binding Path=Setting1}"></TextBox>
<TextBox Name="Setting2Textbox" Text="{Binding Path=Setting2}"></TextBox>
<TextBox Name="Setting3Textbox" Text="{Binding Path=Setting3}"></TextBox>
</Grid>
</Window>
How do you acheive two-way binding in this situation? I've tried what you see above (and so much more) but nothing works!
Have you set the DataContext property of the window to your instance of AppSettings?
public SettingsDialog(SettingsObject settings)
{
InitializeComponent();
//While this line should work above InitializeComponent,
// it's a good idea to put your code afterwards.
this.AppSettings = settings;
//This hooks up the windows data source to your object.
this.DataContext = settings;
}

Categories

Resources