How can I restore the state of a ComboBox? - c#

I have an array of objects NamedFoos in my ViewModel that represents the available choices for a combobox control.
The problem is that when I serialize the bound variable and then deserialize it again in a later run of the application, the combobox does not show the value of the object it is bound to.
The combobox is empty upon startup.
The source of the problem appears to be that the newly created item is not recognized as an element of the collection.
I wrote a little test app and can reproduce the problem by simply assigning a value to the underlying field of the bound variable (see code below).
If I change the assignment from
_selectedFoo = new Bar(2);
to
_selectedFoo = NamedFoos[1];
it works as expected.
I'm aware that de-/serializing just the index of the selection would solve the problem, however serializing the entire object is a requirement.
How can I let the combobox display the restored value even if it's not part of the ItemsSource collection?
xaml
<Window x:Class="test_app.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:test_app"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel x:Name="ViewModel"/>
</Window.DataContext>
<StackPanel>
<ComboBox ItemsSource="{Binding NamedFoos}"
SelectedItem="{Binding SelectedFoo}"
DisplayMemberPath="ComboBoxDisplayName"/>
</StackPanel>
ViewModel
using System.Runtime.Serialization;
namespace test_app
{
public class ViewModel
{
private Foo _selectedFoo;
public ViewModel()
{
_selectedFoo = new Bar(2);
}
public Foo[] NamedFoos { get; } =
{
new Foo(1),
new Bar(2)
};
public Foo SelectedFoo
{
get => _selectedFoo;
set => _selectedFoo = value;
}
[DataContract]
public class Foo
{
[DataMember]
public int Num { get; set; }
public Foo(int num)
{
Num = num;
}
public virtual string ComboBoxDisplayName => "foo";
}
class Bar : Foo
{
public Bar(int num) : base(num)
{
}
public override string ComboBoxDisplayName => "bar";
}
}
}

Related

Realtime update of DataGrid C#

I'm creating a UI that visualizes som parts of a simulator. I am supposed to present some of the values in a table format, however I'm unable to get the table to update continuously when its already initialized (it initialized with the correct values, but is then static when you look at it, so you have to go to another page and then back to get the table to update)
This is the code:
<Page x:Class="SimulatorUI.RawDataPage"
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:SimulatorUI"
mc:Ignorable="d"
d:DesignHeight="950" d:DesignWidth="750"
Title="RawDataPage">
<DataGrid Name="dataTable" MinWidth="500" Margin="10 10 10 10" HorizontalContentAlignment="Center" HorizontalAlignment="Center"/>
</Page>
And the c# code looks like this
public partial class RawDataPage : Page
{
List<TankModule> tankList;
public RawDataPage(List<TankModule> list)
{
tankList = list;
InitializeComponent();
List<displayModule> data = loadTable();
dataTable.ItemsSource = data;
Task.Run(() => updateLoop());
}
public List<displayModule> loadTable()
{
List<displayModule> modules = new List<displayModule>();
foreach(TankModule tank in tankList)
{
modules.Add(new displayModule(tank));
}
return modules;
}
internal async Task updateLoop()
{
for (; ; )
{
dataTable.ItemsSource = null;
dataTable.ItemsSource = loadTable();
await Task.Delay(1000);
}
}
}
Data binding works best in these cases.
Change the tankList from List to ObservableCollection to begin with, and bind the DataGrid to tankList. Now, whenever you will add or remove a new item in tankList, the DataGrid will update to reflect the change.
The code-behind should look like this:
// Add this using to use ObservableCollection
using System.Collections.ObjectModel;
public partial class RawDataPage : Page
{
// I've renamed tankList to TankList, as it is a convention to name public properties in Pascal case
public ObservableCollection<TankModule> TankList { get; set; }
public RawDataPage(List<TankModule> list)
{
DataContext = this; // This will bind this instance as the data context of the view
tankList = new ObservableCollection<TankModule>(list);
}
}
In the View:
<Page x:Class="SimulatorUI.RawDataPage"
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:SimulatorUI"
mc:Ignorable="d"
d:DesignHeight="950" d:DesignWidth="750"
Title="RawDataPage">
<DataGrid Name="dataTable" ItemsSource="{Binding TankList}" MinWidth="500" Margin="10 10 10 10" HorizontalContentAlignment="Center" HorizontalAlignment="Center"/>
</Page>
Note: if you modify an item in TankList, the view might not get updated. In that case, the TankModule class must implement INotifyPropertyChanged.
You can also do something like this after modifying the list item to refresh the list:
ObservableCollection<TankModule> copy = TankList;
TankList = null;
TankList = copy;
Also, it is recommended to use a view model instead of doing all these in the code-behind.

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>();
}
}

Populating a DataGrid with a List from a custom Class

I have a class with a property that holds a list of a custom object.
// MySongs.cs
public class MySongs
{
public List<Song> Songs = new List<Song>();
}
The Songs property gets populated in MainWindow().
// MainWindow.xaml.cs
MySongs.Songs.Add(new Song("Hey Jude", "The Beatles"));
How can I display the MySongs.Songs list in a DataGrid with Title and Artist as headers?
Thanks!
Edit (10/27):
Here is the XAML in MainWindow.xaml:
<Window x:Class="MySongsUI.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:MySongsUI"
mc:Ignorable="d"
Title="My Songs" Height="450" Width="800">
<Grid>
<!-- DataGrid with Songs -->
<DataGrid x:Name="songsDataGrid" />
</Grid>
</Window>
Here is the C# in MySongs.cs:
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
namespace MySongsUI
{
public static class MySongs
{
public static ObservableCollection<Song> Songs = new ObservableCollection<Song>();
}
}
Here is the C# in Song.cs:
namespace MySongsUI
{
public class Song
{
public string Title { get; set; }
public string Artist { get; set; }
// CONSTRUCTOR
public Song(string title, string artist)
{
Title = title;
Artist = artist;
}
}
}
I guess I'm not sure of the best way to make the MySongs class be recognized in the XAML of MainWindow.xaml so I can bind it to the DataGrid.
Thank you!
Here is a very simple example code that will auto generate all your columns according to the properties of the Song class:
<DataGrid ItemsSource="{Binding MySongs}" AutoGenerateColumns="True">
</DataGrid >
If you want anything custom, you will need to style it.
Don't forget to set the list to be an ObservableCollection and the Song class to inherit from NotificationObject if you want to make changes to them on runtime.
Hope this helps..
Edit:
Here is how it should look like:
MainWindlow:
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<local:MainView />
</Window>
MainView.xaml.cs:
public MainView()
{
DataContext = new MainViewModel();
InitializeComponent();
}
MainView.xaml:
<DataGrid ItemsSource="{Binding local:MySongs.Songs}" AutoGenerateColumns="True">
</DataGrid >
MainViewModel.cs:
public class MainViewModel
{
public MySongs MySongs { get; set; }
public MainViewModel()
{
MySongs = new MySongs()
{
Songs = new ObservableCollection<Song>()
{
new Song("Hey Jude", "The Beatles")
}
};
}
}
You will have to implement INotifyPropertyChanged on MainViewModel, Song and MySongs in order to support runtime change of the data.

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.

Missing something on WPF Binding

Just starting WPF & cannot get simple WPF Binding on DataGrid to work and I do not know how to debug. The bound class initializer executes but nothing shows on DataGrid. Minimum code behind and I kept the XAML & bound objects as simple as possible. Thank you for any help.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfBinding"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" enter code heremc:Ignorable="d" x:Class="WpfBinding.MainWindow"
xmlns:syncfusion="http://schemas.syncfusion.com/wpf"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<local:Simple x:Key="keySimple"/>
</Window.Resources>
<Grid>
<DataGrid x:Name="dg" AutoGenerateColumns="True"
DataContext="{Binding Source={StaticResource keySimple}}"
ItemsSource="{Binding Path=Numbers}">
</DataGrid>
</Grid>
</Window>
namespace WpfBinding
{
public class Simple
{
public List<Number> Numbers = new List<Number>();
public Simple()
{
Numbers.Add(new Number(5));
Numbers.Add(new Number(6));
}
}
public class Number
{
private int nmb;
public Number(int x) { nmb = x; }
}
}
Bindings works only on properties and not member variables.
Just change your class to
public class Simple
{
public List<Number> _numbers = new List<Number>();
public List<Number> Numbers { get { return _numbers; } }
public Simple()
{
_numbers.Add(new Number(5));
_numbers.Add(new Number(6));
}
}
public class Number
{
public int NMB { get; set; }
public Number(int x) { NMB = x; }
}

Categories

Resources