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.
Related
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 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}"/>
I'm trying to realize a simple example of a UserControl, showing in a TextBox the current DateTime, updated four times each second.
I create a simple user control:
<UserControl x:Class="UC.TestUC"
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:UC"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="100">
<d:UserControl.DataContext>
<local:TestUC_VM/>
</d:UserControl.DataContext>
<Grid Background="Azure">
<TextBox Text="{Binding TestString}"/>
</Grid>
</UserControl>
Where its ViewModel is:
namespace UC
{
public class TestUC_VM : INotifyPropertyChanged
{
private string _testString;
public string TestString
{
get => _testString;
set
{
if (value == _testString) return;
_testString = value;
OnPropertyChanged();
}
}
public TestUC_VM()
{
TestString = "Test string.";
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow XAML:
<Window x:Class="UC.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:UC"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="200">
<Window.DataContext>
<local:MainWindow_VM/>
</Window.DataContext>
<Window.Resources>
<local:TestUC_VM x:Key="TestUC_VM"/>
</Window.Resources>
<Grid>
<local:TestUC DataContext="{StaticResource TestUC_VM}"/>
</Grid>
</Window>
And its ViewModel:
namespace UC
{
public class MainWindow_VM
{
public TestUC_VM _uc_VM;
public MainWindow_VM()
{
_uc_VM = new TestUC_VM();
Task.Run(() => ChangeString());
}
public async Task ChangeString()
{
while (true)
{
_uc_VM.TestString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
await Task.Delay(250);
}
}
}
}
Even though I see with debugger that I'm passing through the TestString setter, the MainWindow is not updated.
I'm quite sure I'm missing something trivial in setting DataContext of UC in MainWindow, but I've not been able to find what after several hours of browsing and thinking.
Any help appreciated.
The expression
<local:TestUC DataContext="{StaticResource TestUC_VM}"/>
assigns the value of the TestUC_VM resource to the UserControl's DataContext. This is a different object than the _uc_VM member of the main view model, which you are later updating.
Turn the member into a public property
public TestUC_VM UcVm { get; } = new TestUC_VM();
and write
<local:TestUC DataContext="{Binding UcVm}"/>
Update the view model like this:
UcVm.TestString = ...
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.
Consider following XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding Dic[foo]}" />
<Button Content="test" Click="Button_Click" />
</StackPanel>
</Window>
And Backing code:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public Dictionary<string, string> Dic { get; set; }
public MainWindow()
{
InitializeComponent();
Dic = new Dictionary<string, string>();
Dic.Add("foo", "bar");
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Doesn't work :(
Dic["foo"] = "YEAH!";
}
}
}
Here TextBlock properly binds to dictionary item "foo". But how to make it to update when its value is changed?
You need to raise a change notification for the indexer using Binding.IndexerName as property name, you might want to encapsulate that in a new class inheriting or managing Dictionary.
have your dictionnary be a dictionnary(of string, DescriptionObject) Where DescriptionObject has a notifying string property, implements PropertyChanged and has a ToString override.
Then you add (foo, fooDescription) to your dictionnary. If you change fooDescription in your ButtonClick handler, the TextBlock will change too.
You need to add indexer to your code like that:
private Dictionary<string, string> Dic { get; set; }
public string this[string key]
{
get { return Dic[key]; }
set
{
if(key != null && Dic[key] != value)
Dic[key] = value;
OnPropertyChanged("Item[" + key + "]");
}
}
Then, in the xaml you make binding to the indexer, and when the item change it will be notify:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<TextBlock Text="{Binding [foo]}" />
<Button Content="test" Click="Button_Click" />
</StackPanel>
</Window>