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.
Related
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.
So I have a bunch of classes that are derived from some base class. I have classes (collectors) that have a methods that returns collections of these classes. I also have a TabControl where each tab has custom control that contains a DataGrid. There is a ViewModel for these custom controls. In a ViewModel I have collection of base class elements that are returned by collectors. I want to bind DataGrids to these collections and generate columns automatically, but derived classes have different properties and base class doesn't have any properties that should be shown.
internal class ElementsInfoViewModel : INotifyPropertyChanged
{
private readonly ElementScaner _elementScaner;
public ElementsInfoViewModel(ElementScaner elementScaner)
{
_elementScaner = elementScaner;
}
public ReadOnlyCollection<SystemElement> ShownElements => _elementScaner.Elements;
}
<UserControl x:Class="SysSpy.Desktop.Controls.ElementsInfoTabItemContent"
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:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:local="clr-namespace:SysSpy.Desktop.Controls"
mc:Ignorable="d">
<Grid>
<toolkit:DataGrid ItemsSource="{Binding ShownElements}">
</toolkit:DataGrid>
</Grid>
</UserControl>
I removed everything unnecessary from code.
The idea that comes up to my mind is to cast somehow collection of base type to collection of derived type (with IValueConverter possibly), but as collection is updated many times per second, reflection might be bad solution for this
UPD
TabControl is binded to some collection in main vm
internal class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
Test = new ObservableCollection<ElementsInfoViewModel>();
var certificatesCollector = new CertificatesCollector();
var certificatesScaner = new ElementScaner(certificatesCollector, "Certificates");
certificatesScaner.Scan();
var certifVM = new ElementsInfoViewModel(certificatesScaner);
Test.Add(certifVM);
}
public ObservableCollection<ElementsInfoViewModel> Test { get; set;
}
}
<Window x:Class="SysSpy.Desktop.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:SysSpy.Desktop"
xmlns:viewModel="clr-namespace:SysSpy.Desktop.ViewModels"
xmlns:controls="clr-namespace:SysSpy.Desktop.Controls"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<viewModel:MainViewModel x:Key="viewModelSource"/>
</Window.Resources>
<Window.DataContext>
<Binding Source="{StaticResource viewModelSource}"/>
</Window.DataContext>
<DockPanel LastChildFill="True">
<Grid>
<TabControl ItemsSource="{Binding Test}">
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModel:ElementsInfoViewModel">
<controls:ElementsInfoTabItemHeader/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="viewModel:ElementsInfoViewModel">
<controls:ElementsInfoTabItemContent/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</DockPanel>
</Window>
I don't know if this is what you need, but I have another option.
Since DataGrid doesn't have a method to dinamically bind the columns, you can extend it and make it on your own.
For example in this way:
class MyDataGrid : DataGrid
{
public MyDataGrid() : base()
{
}
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (newValue != null)
{
var enumerator = newValue.GetEnumerator();
if (enumerator.MoveNext())
{
Columns.Clear();
var firstElement = enumerator.Current;
var actualType = firstElement.GetType();
foreach (var prop in actualType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanRead))
{
Columns.Add(new DataGridTextColumn
{
Header = prop.Name,
Binding = new Binding(prop.Name)
});
}
}
}
}
}
I wrote a simple project for test it and it works in my environment.
First object type (Person)
class Person
{
public string Name { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
}
Second object type (Car)
class Car
{
public string Name { get; set; }
public int HP { get; set; }
}
The element you called SystemElement (Title is used as tab header)
class SystemElement
{
public SystemElement(string title, IList<object> elements)
{
Title = title;
Elements = new ReadOnlyCollection<object>(elements);
}
public string Title { get; set; }
public ReadOnlyCollection<object> Elements { get; }
}
ElementScanner that extracts the elements to show. In my test the list is set in the constructor:
class ElementScanner
{
public ElementScanner()
{
var data = new List<SystemElement>();
data.Add(new SystemElement("People", new List<object>
{
new Person { Name = "John", Surname = "Doe", Age = 22},
new Person { Name = "Lenny", Surname = "Pegasus", Age = 30},
new Person { Name = "Duffy", Surname = "Duck", Age = 22}
}));
data.Add(new SystemElement("Cars", new List<object>
{
new Car { Name = "Mercedes", HP = 700 },
new Car { Name = "Red Bull", HP = 650 },
new Car { Name = "Ferrari", HP = 600 }
}));
Elements = new ReadOnlyCollection<SystemElement>(data);
}
public ReadOnlyCollection<SystemElement> Elements { get; }
}
Now we have the ViewModel. In my example I did't use the INotifyPropertyChanged since properties will never change (it's a test project).
class ElementsInfoViewModel
{
public ElementsInfoViewModel()
{
var elementScanner = new ElementScanner();
Elements = elementScanner.Elements;
}
public ReadOnlyCollection<SystemElement> Elements { get; }
}
Now move to the view side. First of all we have the UserControl that rappresents the content of each tab. So it will show a DataGrid of type MyDataGrid:
<UserControl x:Class="Stack.ElementInfoView"
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:Stack"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<local:MyDataGrid ItemsSource="{Binding Elements}" >
</local:MyDataGrid>
</Grid>
</UserControl>
Finally we have the main view that will merge all togather:
<Window x:Class="Stack.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:Stack"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ElementsInfoViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding Elements}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Title}" />
</Style>
</TabControl.ItemContainerStyle>
<TabControl.ContentTemplate>
<DataTemplate DataType="local:ElementsInfoViewModel">
<local:ElementInfoView></local:ElementInfoView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
This is what we have (and I suppose it is what you want):
I don't know if it works, but have you tried with Generics?
I mean something like this:
internal class ElementsInfoViewModel<T> : INotifyPropertyChanged
where T : SystemElement
{
public ElementsInfoViewModel(ElementScaner elementScaner)
{
var list = new List<T>();
foreach (var el in elementScanner.Elements)
{
list.Add(el as T);
}
ShownElements = new ReadOnlyCollection<T>(list);
}
public ReadOnlyCollection<T> ShownElements { get; }
}
I write it in the browser, so my code can be wrong, but take the idea.
If it could be nice, the best would be to have the generic version of ElementScanner (something like ElementScanner< T > ).
Let us know if it will work
I have this very simple viewModel:
class ViewModel : IDataErrorInfo
{
public Producto myProduct { get; set; }
public string PR { get; set; }
public ViewModel()
{
myProduct = new Producto { ID = 1, Name = "Product 1" };
PR = "Test";
}
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
string sError = "";
return sError;
}
}
}
And this simple view:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<TextBox Height="40" Width="200" Text="{Binding Path=myProduct.Name,ValidatesOnDataErrors=True}"/>
<TextBox Height="40" Width="200" Text="{Binding Path=PR,ValidatesOnDataErrors=True}"/>
</StackPanel>
</Grid>
Can anybody tell me why validation event is fired for property PR but not for myProduct?
I can't manage to validate fields from an exposed object of the viewmodel! Anyone please!!!
{Binding Path=myProduct.Name, …
For that binding to utilize IDataErrorInfo, the type of myProduct has to implement IDataErrorInfo too. Just like you need to implement INotifyPropertyChanged for subobjects, you need to implement the error info interface too for each subobject.
I have custom user control with the only property - SubHeader.
<UserControl x:Class="ExpensesManager.TopSection"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<StackPanel>
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
public partial class TopSection : UserControl
{
public TopSection()
{
this.InitializeComponent();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
There are two usages in xaml. First with the constant text:
...
<my:TopSection SubHeaderText="Constant text"/>
...
Another one using binding:
<Page x:Class="MyNamespace.MyPage"
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:my="clr-namespace:My"
mc:Ignorable="d"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
...
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
...
</Page>
My page code behind:
public partial class MyPage : Page
{
private MyModel myModel;
public MyModel MyModel
{
get
{
return this.myModel?? (this.myModel = new MyModel());
}
}
public MyPage(MyEntity entity)
{
this.InitializeComponent();
this.MyModel.MyEntity = entity;
}
}
MyModel code:
public class MyModel : NotificationObject
{
private MyEntity myEntity;
private string subHeaderText;
public MyEntity MyEntity
{
get
{
return this.myEntity;
}
set
{
if (this.myEntity!= value)
{
this.myEntity= value;
this.RaisePropertyChanged(() => this.MyEntity);
this.RaisePropertyChanged(() => this.SubHeaderText);
}
}
}
public string SubHeaderText
{
get
{
return string.Format("Name is {0}.", this.myEntity.Name);
}
}
}
The problem is that second one doesn't work. If I pass the constant text - it is displayed, if I use binding to the other property - nothing is displayed.
Does anybody knows what's wrong with the code? Thanks.
The problem is you set DataContext on the UserControl element. It will cause the following binding
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText}"/>
to be relative to that DataContext, which is UserControl itself - so it cannot find the value.
To fix this, I suggest you not set DataContext on the UserControl, but the StackPanel inside:
<UserControl x:Class="ExpensesManager.TopSection"
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">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource AncesterType=UserControl}}">
<Label Name="Header" Content="Constant header text" Style="{StaticResource Header}"/>
<Label Name="SubHeader" Content="{Binding SubHeaderText}" Style="{StaticResource SubHeader}"/>
</StackPanel>
Many people set DataContext on UserControl but that's really BAD. When you use the UserControl later, you have no idea the DataContext is actually set internally and will not respect the outside DataContext - really confusing. This rule also applies to other properties.
MyModel is a property in your DataContext? Try to check what object is your DataContext. If your data context is an object of your class MyModel you doesn't need the MyModel. part in your binding.
This kind of bindings always are to objects in your data context.
Hope this tips helps.
Declare your UserControl like this:
<my:TopSection
x:Name="myControl">
Then change your binding to this:
<my:TopSection SubHeaderText="{Binding MyModel.SubHeaderText, ElementName=myControl}"/>
You didn't set the Model in your UserControl
public partial class TopSection : UserControl
{
public class SampleViewModel { get; set; }
public TopSection()
{
this.InitializeComponent();
this.DataContext = new SampleViewModel();
}
public static readonly DependencyProperty SubHeaderTextProperty =
DependencyProperty.Register("SubHeaderText", typeof(string), typeof(TopSection));
public string SubHeaderText
{
get { return (string)GetValue(SubHeaderTextProperty); }
set { SetValue(SubHeaderTextProperty, value); }
}
}
Update
Since you don't want Model to known to the View. Create a ViewModel
public class SampleViewModel : NotificationObject
{
public class MyModel { get; set; }
public class SampleViewModel()
{
MyModel = new MyModel() { SubHeaderText = "Sample" };
RaisePropertyChange("MyModel");
}
}
I am having trouble accessing my ViewModel when working with my view.
I have a project named BankManagerApplication. Within that I have the various files associated with a new WPF application. I have created three seperate folders Model, ViewModel and View.
At the moment there is a UserModel class in the Model folder with the following fields;
namespace BankManagerApplication.Model
{
public class UserModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public double AccountBallance { get; set; }
}
}
a blank view in the View folder with just a grid inside;
<Window x:Class="BankManagerApplication.View.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindowView" Height="300" Width="300">
<Grid>
</Grid>
</Window>
and also a blank ViewModel in the ViewModel folder;
namespace BankManagerApplication.ViewModel
{
public class MainWindowViewModel
{
}
}
when i try to reference the ViewModel in my XAML like so;
<Window x:Class="BankManagerApplication.View.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindowView" Height="300" Width="300"
xmlns:viewmodel="clr-namespace:BankManagerApplication.ViewModel">
<Grid>
<viewmodel:MainWindowViewModel></viewmodel:MainWindowViewModel>
</Grid>
</Window>
i get the error
The name 'MainWindowViewModel does not exist in the namespace
"clr-namespace:BankManagerApplication.ViewModel'
I have only just started learning WPF and this error is throwing me off before I have really begun
You cannot add it to a Grid control because it is not a UIElement. Your viewmodel will be the DataContext of your view:
<Window x:Class="BankManagerApplication.View.MainWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindowView" Height="300" Width="300"
xmlns:viewmodel="clr-namespace:BankManagerApplication.ViewModel">
<Window.DataContext>
<viewmodel:MainWindowViewModel></viewmodel:MainWindowViewModel>
</Window.DataContext>
<Grid>
</Grid>