I have the following Classes:
Item
public class Item : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private string name;
public string Name
{
get
{ return name; }
set
{
if (value != name)
{
ClearError("Name");
if (string.IsNullOrEmpty(value) || value.Trim() == "")
SetError("Name", "Required Value");
name = value;
}
}
}
private List<MedicineComposition> medicineCompositions;
public List<MedicineComposition> MedicineCompositions
{
set { medicineCompositions = value; }
get { return medicineCompositions; }
}
}
MedicineComposition
public class MedicineComposition : INotifyPropertyChanged, IDataErrorInfo
{
private int? id;
public int? ID
{
get
{ return id; }
set
{ id = value; }
}
private Item item;
public Item Item
{
get
{ return item; }
set
{
if (item != value)
{
ClearError("Item");
if (value == null)
SetError("Item", "Required Value");
item = value;
}
}
}
private Component component;
public Component Component
{
get
{ return component; }
set
{
if (component != value)
{
ClearError("Component");
if (value == null)
SetError("Component", "Required Value");
component = value;
}
}
}
}
Component which has only id and Name
and the following functions that bring data from database and make the list of my objects:
GetItems in Item Class
public static List<Item> GetAllItems
{
get
{
List<Item> MyItems = new List<Item>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_All_Item", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
Item i = new Item();
if (!(rd["ID"] is DBNull))
i.ID = System.Int32.Parse(rd["ID"].ToString());
i.Name = rd["Name"].ToString();
i.MedicineCompositions = MedicineComposition.GetAllByItem(i);
MyItems.Add(i);
}
rd.Close();
}
catch
{
MyItems = null;
}
finally
{
con.Close();
}
return MyItems;
}
GetAllByItem in MedicalCompositions
public static List<MedicineComposition> GetAllByItem(Item i)
{
List<MedicineComposition> MyMedicineCompositions = new List<MedicineComposition>();
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("#ID", i.ID);
com.Parameters.Add(pr);
try
{
con.Open();
SqlDataReader rd = com.ExecuteReader();
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
MyMedicineCompositions.Add(m);
}
rd.Close();
}
catch
{
MyMedicineCompositions = null;
}
finally
{
con.Close();
}
return MyMedicineCompositions;
}
it's like to use mvvm because it let you deal with objects instead of datatable, but when i use the previous shape of class structure i have the following problems:
i have at least 1000 records in Item Table in database so when i call GetAllItems i have slow performance especially when the database in not on local computer.
i tried to load Items when splash screen on, it takes times but take medium performance
on each update on Item table i should recall GetAllItems so slow back
my questions is where is the problem that i have in creating class, and is this the best way to structure the class in mvvm
I dont think your user need to see all the 1000 items at a glance, not even the many thousand of composition and components related.
I situations like this I would:
Filter the data. Ask the user for the Item name, category or what else.
Delay load. At first load only the (filtered) Items. When the user select an Item switch to an "Item details" View and load the related data (composition and components).
A few things you could improve here, for example:
Given we are talking about MedicalComposition it might not be best of ideas to have nullable unique identifier
If you have multiple classes consisting only of id and name you could use KeyValuePair<> or Tuple<> instead
Implement a base class, say ModelBase that implements INotifyPropertyChanged
Implement repository pattern for database related action, cache/page results if possible
If not yet done, move data- and/or time intensive operations into separate thread(s)
It's a little confusing that on Item you have IEnumerable of MedicineCompositions but also, in MedicineComposition you have the Item, too? Maybe you don't need it at all or related Item.Id would be enough?
You could add a method to your repository to only return items that have been added/modified/removed since <timestamp> and only update what is necessary in your Items collection
You could make some of the properties Lazy<>
Utilize TAP (Task-based Asynchronous Pattern)
Below is "one go" for your problem w/o blocking the UI thread. It's far from complete but still. Thread.Sleeps in repositories are mimicking your database query delays
View\MainWindow.xaml
Codebehind contains just InitializeComponents.
<Window x:Class="WpfApplication1.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModel"
Title="MainWindow"
Height="300"
Width="250">
<Window.DataContext>
<viewModel:MainViewModel />
</Window.DataContext>
<!-- Layout root -->
<Grid x:Name="ContentPanel" Margin="12,0,12,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- Status label -->
<Label Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="Bisque"
Margin="0,3,0,3"
Content="{Binding Status}" />
<!-- Controls -->
<StackPanel Grid.Row="1">
<Label Content="Items" />
<!-- Items combo -->
<ComboBox HorizontalAlignment="Stretch"
MaxDropDownHeight="120"
VerticalAlignment="Top"
Width="Auto"
Margin="0,0,0,5"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name" />
<!-- Medicine components -->
<ItemsControl ItemsSource="{Binding SelectedItem.MedicineCompositions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Name}" />
<!-- Components -->
<ItemsControl ItemsSource="{Binding Components}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text=" * " />
<Run Text="{Binding Name}" />
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
ViewModel\MainViewModel
public class MainViewModel : ViewModelBase
{
private string _status;
private Item _selectedItem;
private ObservableCollection<Item> _items;
public MainViewModel()
:this(new ItemRepository(), new MedicineCompositionRepository())
{}
public MainViewModel(IRepository<Item> itemRepository, IRepository<MedicineComposition> medicineCompositionRepository)
{
ItemRepository = itemRepository;
MedicineCompositionRepository = medicineCompositionRepository;
Task.Run(() => LoadItemsData());
}
public IRepository<Item> ItemRepository { get; set; }
public IRepository<MedicineComposition> MedicineCompositionRepository { get; set; }
public Item SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
Task.Run(() => LoadMedicineCompositionsData(_selectedItem));
}
}
public ObservableCollection<Item> Items
{
get { return _items; }
set { _items = value; OnPropertyChanged(); }
}
public string Status
{
get { return _status; }
set { _status = value; OnPropertyChanged(); }
}
private async Task LoadItemsData()
{
Status = "Loading items...";
var result = await ItemRepository.GetAll();
Items = new ObservableCollection<Item>(result);
Status = "Idle";
}
private async Task LoadMedicineCompositionsData(Item item)
{
if (item.MedicineCompositions != null)
return;
Status = string.Format("Loading compositions for {0}...", item.Name);
var result = await MedicineCompositionRepository.GetById(item.Id);
SelectedItem.MedicineCompositions = result;
Status = "Idle";
}
}
Model
public class Component : ModelBase
{}
public class MedicineComposition : ModelBase
{
private IEnumerable<Component> _component;
public IEnumerable<Component> Components
{
get { return _component; }
set { _component = value; OnPropertyChanged(); }
}
}
public class Item : ModelBase
{
private IEnumerable<MedicineComposition> _medicineCompositions;
public IEnumerable<MedicineComposition> MedicineCompositions
{
get { return _medicineCompositions; }
set { _medicineCompositions = value; OnPropertyChanged(); }
}
}
public abstract class ModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _id;
private string _name;
public int Id
{
get { return _id; }
set { _id = value; OnPropertyChanged(); }
}
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged(); }
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Repository
public interface IRepository<T> where T : class
{
Task<IEnumerable<T>> GetAll();
Task<IEnumerable<T>> GetById(int id);
}
public class ItemRepository : IRepository<Item>
{
private readonly IList<Item> _mockItems;
public ItemRepository()
{
_mockItems = new List<Item>();
for (int i = 0; i < 100; i++)
_mockItems.Add(new Item { Id = i, Name = string.Format("Item #{0}", i), MedicineCompositions = null });
}
public Task<IEnumerable<Item>> GetAll()
{
Thread.Sleep(1500);
return Task.FromResult((IEnumerable<Item>) _mockItems);
}
public Task<IEnumerable<Item>> GetById(int id)
{
throw new NotImplementedException();
}
}
public class MedicineCompositionRepository : IRepository<MedicineComposition>
{
private readonly Random _random;
public MedicineCompositionRepository()
{
_random = new Random();
}
public Task<IEnumerable<MedicineComposition>> GetAll()
{
throw new NotImplementedException();
}
public Task<IEnumerable<MedicineComposition>> GetById(int id)
{
// since we are mocking, id is actually ignored
var compositions = new List<MedicineComposition>();
int compositionsCount = _random.Next(1, 3);
for (int i = 0; i <= compositionsCount; i++)
{
var components = new List<Component>();
int componentsCount = _random.Next(1, 3);
for (int j = 0; j <= componentsCount; j++)
components.Add(new Component {Id = j, Name = string.Format("Component #1{0}", j)});
compositions.Add(new MedicineComposition { Id = i, Name = string.Format("MedicalComposition #{0}", i), Components = components });
}
Thread.Sleep(500);
return Task.FromResult((IEnumerable<MedicineComposition>) compositions);
}
}
Instead of returning List, return IEnumerable and yield results as they are needed. Obviously it would only improve performance, when you are not reading all the results, which is actually true in most cases. To do that you would have to remove catch, because you cannot have yield and catch together. The catch could go around con.Open and ExecuteReader and in catch you can yield break:
public static IEnumerable<MedicineComposition> GetAllByItem(Item i)
{
SqlConnection con = new SqlConnection(BaseDataBase.ConnectionString);
SqlCommand com = new SqlCommand("sp_Get_ByItemID_MedicineComposition", con);
com.CommandType = System.Data.CommandType.StoredProcedure;
SqlParameter pr = new SqlParameter("#ID", i.ID);
com.Parameters.Add(pr);
try
{
SqlDataReader rd;
try
{
con.Open();
rd = com.ExecuteReader();
}
catch { yield break;}
while (rd.Read())
{
MedicineComposition m = new MedicineComposition() { };
if (!(rd["ID"] is DBNull))
m.ID = Int32.Parse(rd["ID"].ToString());
if (!(rd["ComponentID"] is DBNull))
m.Component = Component.GetByID(Int32.Parse(rd["ComponentID"].ToString()));
m.Item = i;
yield return m;
}
rd.Close();
}
finally
{
con.Close();
}
}
Now in case of an exception this is no longer returning null, but can return few items or even empty enumeration. I would rather move the catch to caller of this getter.
If you need for some reason count of returned items, call GetAllByItem(item).ToArray(). This will enumerate all the items once and gets the length for you. Definitely don't call the enumeration twice to get the length and then enumerate the items:
var length = GetAllByItem(item).Count();// this will get all the items from the db
foreach(var i in GetAllByItem(item)) // this will get all the items from the db again
Rather do this:
var list = GetAllByItem(item); // this will get all the items and now you have the length and the items.
Obviously if you need the length for some reason, there is no point in changing to IEnumerable, only for better abstraction.
Other improvement could be, to create the connection only once instead of every time on calling the getter. That is possible only, if you know it won't cause any harm.
Assign the dataset into the constructor of your ObservableCollection property. Else your view will update via a PropertyChanged notification for each item that your ObservableCollection performs an Add operation.
Try this:
var items = services.LoadItems();
myObservableCollection = new ObservableCollection<somedatatype>(items);
This type of assignment will notify your view once instead of the current way your implementation does which is 1000 times.
Related
I see several other posts about this but I cannot seem to understand exactly how to get this working properly for my usage.
Here is what I have in a nutshell.
I have two Comboboxes--Role and Position.
I have both of these bound to an ObservableCollection which has Enum Values Converted to strings loaded into it on instantiation.
<ComboBox x:Name="empRoleCB" ItemsSource="{Binding Role}" SelectedItem="{Binding RoleStr}"/>
<ComboBox x:Name="empPositionCB" ItemsSource="{Binding Pos}" SelectedItem="{Binding PosStr}"/>
In my ViewModel:
public abstract class EmployeeMenuVMBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if(!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
class EmployeeMenuVM : EmployeeMenuVMBase
{
private ObservableCollection<string> _pos = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Positions)));
private ObservableCollection<string> _role = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Roles)));
public ObservableCollection<string> Pos { get => _pos; }
public ObservableCollection<string> Role { get => _role; }
public string RoleStr
{
get => _roleStr;
set => SetProperty(ref _roleStr, value);
}
public string PosStr
{
get => _posStr;
set => SetProperty(ref _posStr, value);
}
}
What I want to happen is when a Role is selected, based on that selection, only certain Positions should be shown. For instance if I select "Customer Service" as a Role, then Position should only contain "Manager", "CSS" and "None". If Role is "Admin" then Position should only contain "None", and so on and so forth.
The struggle I have is how to filter this properly. I see something with using CollectionViewSource but I am unsure how to get this to work with my example.
I have 5 roles and each role will have a different list of positions that need to be shown.
What is the best way to make this work with MINIMAL extra code or XAML?
One of the things I really dislike about WPF is seemingly simple things need huge amounts of code to make them work properly many times.
First, if you think that WPF is complicated. So, you are using it wrongly.
I suggest you to use the Filter of CollectionViewSource as flow:
<ComboBox x:Name="empPositionCB" ItemsSource="{Binding MyPositionFilter}" SelectionChanged="RoleComboBox_SelectionChanged" ....../>
public ICollectionView MyPositionFilter { get; set; }
//ctor
public MyUserControlOrWindow()
{
//Before InitComponent()
this.MyPositionFilter = new CollectionViewSource { Source = MyPosObservableCollection }.View;
InitComponent();
}
public void RoleComboBox_SelectionChanged(object sender,EventArgs e)
{
//Get the selected Role (the ? is to prevent NullException (VS 2015 >))
Role r = empRoleCB.SelectedItem as Role;
//Apply the filter
this.MyPositionFilter.Filter = item =>
{
//Make you sure to convert correcteley your Enumeration, I used it here like a class
Position p = item as Position;
//Put your condition here. For example:
return r.ToLowers().Contains(p.ToLower());
//Or
return (r != null && r.Length >= p.Length);
};
}
The filter does not change your Collection, All hidden item stay in your ObservableCollection.
This can all be done in your ViewModel by changing the value of the Positions (Pos) observable collection when the role changes.
class EmployeeMenuVM : EmployeeMenuVMBase
{
public EmployeeMenuVM()
{
var emptyPositions = new List<Global.Positions>()
{ Global.Positions.None };
_rolePositions.Add(Global.Roles.None, emptyPositions);
var customerServicePositions = new List<Global.Positions>()
{ Global.Positions.None, Global.Positions.CSS, Global.Positions.Manager };
_rolePositions.Add(Global.Roles.CustomerService, customerServicePositions);
}
private Dictionary<Global.Roles, List<Global.Positions>> _rolePositions = new Dictionary<Global.Roles, List<Global.Positions>>();
private string _roleStr;
private string _posStr;
private ObservableCollection<string> _pos = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Positions)));
private ObservableCollection<string> _role = new ObservableCollection<string>(Enum.GetNames(typeof(Global.Roles)));
public ObservableCollection<string> Pos
{
get => _pos;
set
{
SetProperty(ref _pos, value);
}
}
public ObservableCollection<string> Role
{
get => _role;
}
public string RoleStr
{
get => _roleStr;
set
{
if (SetProperty(ref _roleStr, value))
{
Global.Roles role = (Global.Roles)Enum.Parse(typeof(Global.Roles), value);
var positions = _rolePositions[role].Select(p => p.ToString());
Pos = new ObservableCollection<string>(positions);
}
}
}
public string PosStr
{
get => _posStr;
set => SetProperty(ref _posStr, value);
}
}
Here is a working tester code just to see the main idea of how to do the filtering:
MainWindow.xaml
<Window x:Class="WpfApplication3.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:WpfApplication3"
x:Name="ThisView"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="600">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=Roles, ElementName=ThisView}"
SelectedItem="{Binding Path=SelectedRole, ElementName=ThisView}"
Width="300" Height="60"/>
<ComboBox ItemsSource="{Binding Path=PositionCollectionView, ElementName=ThisView}" Width="300" Height="60"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ICollectionView PositionCollectionView { get; set; }
public ObservableCollection<string> Roles { get; set; } = new ObservableCollection<string>();
public ObservableCollection<string> Positions { get; set; } = new ObservableCollection<string>();
private string _selectedRole = String.Empty;
public string SelectedRole
{
get { return _selectedRole; }
set
{
_selectedRole = value;
OnPropertyChanged();
//This Refresh activates the Filter again, so that every time you select a role, this property will call it.
PositionCollectionView.Refresh();
}
}
public MainWindow()
{
PositionCollectionView = CollectionViewSource.GetDefaultView(Positions);
PositionCollectionView.Filter = PositionsFilter;
//use you enums here
Roles.Add("Role1");
Roles.Add("Role2");
Roles.Add("Role3");
Roles.Add("Role4");
Positions.Add("Position1");
Positions.Add("Position2");
Positions.Add("Position3");
Positions.Add("Position4");
InitializeComponent();
}
private bool PositionsFilter(object position)
{
bool result = true;
//place your code according to the Role selected to decide wheather "position" should be in the position list or not
return result;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Hope it helps..
I have tried numerous approaches based on many examples on StackOverflow and elsewhere, but just can't get this to work. I want the list of choices for ComboBox B to be different based on the selected item in ComboBox A. Could someone please help me determine what I'm doing wrong? (I am using MVVMLight.)
Here are the ItemsSources for the two comboboxes:
private ObservableCollection<SetupSF> _sourceForSFList = new ObservableCollection<SetupSF>();
public ObservableCollection<SetupSF> SourceForSFList
{
get { return _sourceForSFList; }
set
{
_sourceForSFList = value;
}
}
private ObservableCollection<SetupSFE> _sourceForSFEList = new ObservableCollection<SetupSFE>();
public ObservableCollection<SetupSFE> SourceForSFEList
{
get { return _sourceForSFEList; }
set
{
_sourceForSFEList = value;
}
}
Here is the selector. I tried using RelayPropertyChanged even though it's an observable collection, but that had no effect. I've verified that contents of the list is updating by setting a breakpoint. The list updates, but apparently does not notify the second combobox. _selectedSF.OwnedSFEs is itself an ObservalbleCollection.
public SetupSubfactor SelectedSF
{
get { return _selectedSF; }
set
{
_selectedSF = value;
if (_selectedSF != null)
{
SourceForSFEList = _selectedSF.OwnedSFEs;
}
}
}
Here is the code for the SF type. It is defined outside the ViewModel. (Could that be the problem?
public class SetupSF : ViewModelBase
{
private string _iD;
public string Id
{
get { return _iD; }
set
{
_iD = value;
RaisePropertyChanged(nameof(Id));
}
}
private string _sfName;
public string SFName
{
get { return _sfName; }
set
{
_sfName = value;
RaisePropertyChanged(nameof(SFName));
}
}
public ObservableCollection<SetupSFE> OwnedSFEs { get; set; }
}
Here is the XAML for the comboboxes:
<ComboBox x:Name="ComboBox A"
Width="350"
ItemsSource="{x:Bind ViewModel.SourceForSFList}"
SelectedItem="{x:Bind ViewModel.SelectedSF, Mode=TwoWay}"
DisplayMemberPath="SFName"/>
<ComboBox x:Name="Comobox B"
Width="350"
ItemsSource="{x:Bind ViewModel.SourceForSFEList}"
SelectedItem="{x:Bind ViewModel.SelectedSFE, Mode=TwoWay}
DisplayMemberPath="SFEName"/>
I believe you want to change your SourceForSFEList to look like this:
private ObservableCollection<SetupSFE> _sourceForSFEList = new ObservableCollection<SetupSFE>();
public ObservableCollection<SetupSFE> SourceForSFEList
{
get { return _sourceForSFEList; }
set
{
_sourceForSFEList.Clear();
if (value != null)
{
foreach (var item in value)
{
_sourceForSFEList.Add(item);
}
}
}
}
The Binding system is calling your "get" once, and is getting the initial Collection that _sourceForSFEList points to. Later, you are changing _sourceForSFEList to point to another collection, but the Binding system doesn't know anything about that collection, and so the UI doesn't update. Once the binding system binds a UIElement to _sourceForSFEList, you need to update that original collection, instead of changing the value of _sourceForSFEList to point to a new collection.
I've tried to infer some of your original classes. Here is a working example that I used to have one CombBox's selection update another's.
public class ViewModel
{
private ObservableCollection<SetupSF> _sourceForSFList = new ObservableCollection<SetupSF>();
public ViewModel()
{
_sourceForSFList = new ObservableCollection<SetupSF>()
{
new SetupSF() { SFName = "One", OwnedSFEs = new ObservableCollection<SetupSFE> () { new SetupSFE() { SFEName = "Three" }, new SetupSFE() { SFEName = "Four" } } },
new SetupSF() { SFName = "Two", OwnedSFEs = new ObservableCollection<SetupSFE> () { new SetupSFE() { SFEName = "Five" }, new SetupSFE() { SFEName = "Six" } } }
};
_sourceForSFEList = new ObservableCollection<SetupSFE>()
{
new SetupSFE() { SFEName = "One"},
new SetupSFE() { SFEName = "Two"}
};
}
private SetupSF _selectedSF;
public SetupSF SelectedSF
{
get { return _selectedSF; }
set
{
_selectedSF = value;
if (_selectedSF != null)
{
SourceForSFEList = _selectedSF.OwnedSFEs;
}
}
}
public ObservableCollection<SetupSF> SourceForSFList
{
get { return _sourceForSFList; }
set
{
_sourceForSFList = value;
}
}
private ObservableCollection<SetupSFE> _sourceForSFEList = new ObservableCollection<SetupSFE>();
public ObservableCollection<SetupSFE> SourceForSFEList
{
get { return _sourceForSFEList; }
set
{
_sourceForSFEList.Clear();
if (value != null)
{
foreach (var item in value)
{
_sourceForSFEList.Add(item);
}
}
}
}
}
public class SetupSF
{
public String SFName { get; set; }
public ObservableCollection<SetupSFE> OwnedSFEs;
}
public class SetupSFE
{
public String SFEName { get; set; }
}
<Page
x:Class="App7.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App7"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="ComboBoxA"
Width="350"
ItemsSource="{Binding SourceForSFList}"
SelectedItem="{Binding SelectedSF, Mode=TwoWay}"
DisplayMemberPath="SFName" Margin="10"/>
<ComboBox x:Name="ComoboxB"
Width="350"
ItemsSource="{Binding SourceForSFEList}"
SelectedItem="{Binding SelectedSFE, Mode=TwoWay}"
DisplayMemberPath="SFEName" Margin="10"/>
</StackPanel>
</Page>
I am developing a small utility using C#/WPF/MVVM which would allow to set the input state of a controller we are using for testing. The communication between the app I am developing and the hardware/our web service communication to the hardware is only one way, meaning that the app will only be able to set the state of the inputs, but not get the states.
Another point to mention is that some types are already defined for this in some other parts of our solution, which are all in F#. To do my app, I am currently using C#. So I did a Unit class to wrap around the LocalControllerTypes.LocalController type defined in F#, containing a lot of needed information.
In order to do that, I have an enum enumerating the InputState possible (currently there is Active or Normal, but that list could potentially grow with time). Also, the number of inputs present on each unit type is different (some have 2, some have 4, some have more), so I have an ItemControl binded on the selected unit's array of Inputs, which unfortunately only contains the Name of the input which I have to display. The unit has 2 other properties related to the inputs it has, InputWriters, which is an array of a type which is used to send the command to the hardware/web service communicating with that hardware, and InputStates, which is an array of InputState for each input it has, as last set in the app (since we can't get the state from the hardware).
Now I would like to bind the IsChecked property of the radio buttons (which is what I define as ItemTemplate of the ItemsControl) to the InputState of the currently SelectedUnit (in my ViewModel). The problem I am having, is that I would somehow need to know the radio button is for which index of the SelectedUnit's Inputs array, in order to get the item at the same index for the SelectedUnit's InputStates property.
Is there any way to achieve this?
MainWindow.xaml:
...
<ItemsControl Grid.Row="1" ItemsSource="{Binding SelectedUnit.LocalControllerInfo.Inputs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10" FontSize="15" Style="{StaticResource TextBlockNormalBase}" Text="{Binding InputName}"/>
<StackPanel Orientation="Horizontal">
<RadioButton Margin="10" Foreground="White" Content="Normal"
IsChecked="{Binding Path=?,
Converter={StaticResource inputToBoolConverter},
ConverterParameter=?}"/>
<RadioButton Margin="10" Foreground="White" Content="Active"
IsChecked="{Binding Path=?,
Converter={StaticResource inputToBoolConverter},
ConverterParameter=?}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
...
Unit.cs:
public class Unit : BindableObject
{
public enum InputState
{
Normal,
Active
}
private LocalControllerTypes.LocalController _localControllerInfo;
private LocalControllerTypes.ArduinoInjector[] _arduinoInjector;
private WebWriter.WebWriter[] _inputWriters;
private SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection _networkSwitchConnection;
private InputState[] _inputStates;
private bool _isUnitConnected;
public Unit(LocalControllerTypes.LocalController localControllerInfo,
LocalControllerTypes.ArduinoInjector[] arduinoInjector,
WebWriter.WebWriter[] inputWriters,
SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection networkSwitchConnection)
{
_localControllerInfo = localControllerInfo;
_arduinoInjector = arduinoInjector;
_inputWriters = inputWriters;
_networkSwitchConnection = networkSwitchConnection;
// This assumption might not always be true, but there is no way for now to get the input state
_inputStates = Enumerable.Repeat(InputState.Normal, _inputWriters.Length).ToArray();
// This assumption might not always be true, but there is no way for now to get the connection state
_isUnitConnected = true;
}
public LocalControllerTypes.LocalController LocalControllerInfo
{
get
{
return _localControllerInfo;
}
set
{
if (_localControllerInfo != value)
{
_localControllerInfo = value;
RaisePropertyChanged();
}
}
}
public LocalControllerTypes.ArduinoInjector[] ArduinoInjectors
{
get
{
return _arduinoInjector;
}
set
{
if (_arduinoInjector != value)
{
_arduinoInjector = value;
RaisePropertyChanged();
}
}
}
public WebWriter.WebWriter[] InputWriters
{
get
{
return _inputWriters;
}
set
{
if (_inputWriters != value)
{
_inputWriters = value;
RaisePropertyChanged();
}
}
}
public SNMPNetworkSwitchConnection.SNMPNetworkSwitchConnection NetworkSwitchConnection
{
get
{
return _networkSwitchConnection;
}
set
{
if (_networkSwitchConnection != value)
{
_networkSwitchConnection = value;
RaisePropertyChanged();
}
}
}
public InputState[] InputStates
{
get
{
return _inputStates;
}
set
{
if (_inputStates != value)
{
_inputStates = value;
RaisePropertyChanged();
}
}
}
public bool IsUnitConnected
{
get
{
return _isUnitConnected;
}
set
{
if (_isUnitConnected != value)
{
_isUnitConnected = value;
RaisePropertyChanged();
}
}
}
}
MainViewModel.cs:
public class MainViewModel : INotifyPropertyChanged
{
private Unit _selectedUnit;
private ObservableCollection<Unit> _units;
private string _reader1RawCardData;
private string _reader2RawCardData;
private int _reader1BitsCount;
private int _reader2BitsCount;
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel(IUnitStore unitStore)
{
UnitStore = unitStore;
// We could use directly the unitstore instead of creating another container and binding on that, but
// not doing so will allow us to add unit filtering further down the road
_units = new ObservableCollection<Unit>(unitStore.Units);
_selectedUnit = _units.First();
_reader1RawCardData = "";
_reader2RawCardData = "";
_reader1BitsCount = 0;
_reader2BitsCount = 0;
}
protected void RaisePropertyChanged([CallerMemberName]string propertName = "")
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertName));
}
}
protected void RefreshUnitStore(object obj)
{
UnitStore.UpdateStore();
Units = new ObservableCollection<Unit>(UnitStore.Units);
SelectedUnit = Units.First();
}
protected void SendReaderCardSwipe(object obj)
{
int unitReaderNumber = (int)obj;
IPAddress arduinoIp = SelectedUnit.LocalControllerInfo.Readers[unitReaderNumber - 1].InjectorIp;
int injectorNumber = SelectedUnit.LocalControllerInfo.Readers[unitReaderNumber - 1].InjectorNumber;
string serviceUrl = SelectedUnit.ArduinoInjectors.Where(injector => injector.Ip.Equals(arduinoIp)).First().Url;
InjectorInterface.CardSwipe<IPAddress>(serviceUrl, arduinoIp, injectorNumber, Reader1BitsCount, Reader1RawCardData);
}
protected void UpdateSelectedUnitConnectionState(object obj)
{
((INetworkConnection.INetworkConnection)SelectedUnit.NetworkSwitchConnection).SetConnection(SelectedUnit.IsUnitConnected);
}
public IUnitStore UnitStore
{
get;
private set;
}
public Unit SelectedUnit
{
get
{
return _selectedUnit;
}
set
{
if (_selectedUnit != value)
{
_selectedUnit = value;
RaisePropertyChanged();
}
}
}
public ObservableCollection<Unit> Units
{
get
{
return _units;
}
set
{
if (_units != value)
{
_units = value;
RaisePropertyChanged();
}
}
}
public string Reader1RawCardData
{
get
{
return _reader1RawCardData;
}
set
{
if (_reader1RawCardData != value)
{
_reader1RawCardData = value;
RaisePropertyChanged();
}
}
}
public string Reader2RawCardData
{
get
{
return _reader2RawCardData;
}
set
{
if (_reader2RawCardData != value)
{
_reader2RawCardData = value;
RaisePropertyChanged();
}
}
}
public int Reader1BitsCount
{
get
{
return _reader1BitsCount;
}
set
{
if (_reader1BitsCount != value)
{
_reader1BitsCount = value;
RaisePropertyChanged();
}
}
}
public int Reader2BitsCount
{
get
{
return _reader2BitsCount;
}
set
{
if (_reader2BitsCount != value)
{
_reader2BitsCount = value;
RaisePropertyChanged();
}
}
}
public ICommand RefreshSourceCommand
{
get
{
return new RelayCommand(RefreshUnitStore);
}
}
public ICommand SendReaderCardSwipeCommand
{
get
{
return new RelayCommand(SendReaderCardSwipe);
}
}
public ICommand UpdateSelectedUnitConnectionStateCommand
{
get
{
return new RelayCommand(UpdateSelectedUnitConnectionState);
}
}
}
Your ItemsControl is bound to SelectedUnit.LocalControllerInfo.Inputs. What is the type of .Inputs?
As written your bindings will not have access to InputState or InputName. That's not really in the scope of "how to identify what array item goes with what enum"
To address your original issue, one possibility would be to nest some tuples and bind to that, a la
List<Tuple<int,State>> States = new List<Tuple<int,State>>();
States.Add(new Tuple<int, State>(1,State.Bar));
States.Add(new Tuple<int, State>(2, State.Foo));
States.Add(new Tuple<int, State>(3, State.Bar));
I have been working in a bespoke control based upon the WPF ComboBox
code below
enter code here<UserControl x:Class="wpfColorCombo.ColorPicker"
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="32" d:DesignWidth="190">
<UserControl.Resources>
<DataTemplate x:Key="LoadedValue">
<StackPanel Orientation="Horizontal">
<TextBlock Width="10"
Height="10"
Margin="5"
Background="Aqua"/>
<TextBlock Text="Aqua"
Margin="5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ComboBox x:Name="colorPickerCombo"
ItemsSource="{Binding Path=FontColors}"
DropDownClosed="colourPickerCombo_DropDownClosed"
SelectedValue="{Binding Path=SelectedFontColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True"
Loaded="colourPickerCombo_Loaded">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="10"
Height="10"
Margin="5"
Background="{Binding Name}"/>
<TextBlock Text="{Binding Name}"
Margin="5"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
The controls code behind:
public partial class ColorPicker : UserControl
{
public ColorPicker()
{
InitializeComponent();
DataContext = new viewModelColorPicker();
}
}
Nothing unusual here except when I come to reload the control with with saved data. I seem to be unable to find a means to get the displayed. I have looked for the last half day for a solution and the nearest I have come is based upon a similar problem in a Grid control. The solution there was to assign the windows resource in this case LoadedValue to the content. In a ComboBox I have Text. I have tried to utilize TextBlock but without a stack panel to aid lay outs I am stuck.
Any help that may get me closer is good :)
Cheers Angry Bobb
I've seen issues like this, mostly these problem occurs when you overwrite the bounded instance.
Like, when you load the data, (for example with xmlserializer) a new instance of the FontColors will overwrite the old one. The combobox is still bounded to the old instance, so the new one doesn't show up.
So, you probably need to reassign/bind the ItemsSource of the Combobox after loading.
This is the code behind
public class viewModelColorPicker : ViewModelBase
{
modelFont colours = new modelFont();
public viewModelColorPicker()
{
}
private ObservableCollection<FontColor> fontColours;
public ObservableCollection<FontColor> FontColours
{
get
{
fontColours = colours.AvalibleColours;
return fontColours;
}
}
private FontColor selectedColour = new FontColor("Black",Brushes.Black);
public FontColor SelectedColour
{
get
{
selectedColour = colours.SelectedColor.Coalesce(new FontColor("Black",Brushes.Black));
return selectedColour;
}
set
{
SetProperty(ref selectedColour, value, "SelectedColour");
colours.SelectedColor = value;
}
}
}
These three classes are the model
public class modelFont : ViewModelBase
{
ObservableCollection<FontColor> availableColours = new ObservableCollection<FontColor>();
public modelFont()
{
AvailableColors AvCol = new AvailableColors();
availableColours = AvCol.GetFontColorsList().ToObservableCollection();
}
public ObservableCollection<FontColor> AvalibleColours
{
get { return availableColours; }
set { SetProperty(ref this.availableColours, value, "AvalibleColours"); }
}
private FontColor selectedColor;
public FontColor SelectedColor
{
get { return selectedColor; }
set { SetProperty(ref this.selectedColor, value, "SelectedColor"); }
}
}
public class FontColor
{
public string Name { get; set; }
public SolidColorBrush Brush { get; set; }
public FontColor(string name, SolidColorBrush brush)
{
Name = name;
Brush = brush;
}
public override bool Equals(System.Object obj)
{
if (obj == null)
{
return false;
}
FontColor p = obj as FontColor;
if ((System.Object)p == null)
{
return false;
}
return (this.Name == p.Name) && (this.Brush.Equals(p.Brush));
}
public bool Equals(FontColor p)
{
if ((object)p == null)
{
return false;
}
return (this.Name == p.Name) && (this.Brush.Equals(p.Brush));
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override string ToString()
{
return "FontColor [Color=" + this.Name + ", " + this.Brush.ToString() + "]";
}
}
}
class AvailableColors : List<FontColor>
{
#region Conversion Utils Static Methods
public static FontColor GetFontColor(SolidColorBrush b)
{
AvailableColors brushList = new AvailableColors();
return brushList.GetFontColorByBrush(b);
}
public static FontColor GetFontColor(string name)
{
AvailableColors brushList = new AvailableColors();
return brushList.GetFontColorByName(name);
}
public static FontColor GetFontColor(Color c)
{
return AvailableColors.GetFontColor(new SolidColorBrush(c));
}
public static int GetFontColorIndex(FontColor c)
{
AvailableColors brushList = new AvailableColors();
int idx = 0;
SolidColorBrush colorBrush = c.Brush;
foreach (FontColor brush in brushList)
{
if (brush.Brush.Color.Equals(colorBrush.Color))
{
break;
}
idx++;
}
return idx;
}
#endregion
public AvailableColors()
: base()
{
this.Init();
}
public FontColor GetFontColorByName(string name)
{
FontColor found = null;
foreach (FontColor b in this)
{
if (b.Name == name)
{
found = b;
break;
}
}
return found;
}
public FontColor GetFontColorByBrush(SolidColorBrush b)
{
FontColor found = null;
foreach (FontColor brush in this)
{
if (brush.Brush.Color.Equals(b.Color))
{
found = brush;
break;
}
}
return found;
}
private void Init()
{
Type brushesType = typeof(Colors);
var properties = brushesType.GetProperties(BindingFlags.Static | BindingFlags.Public);
foreach (var prop in properties)
{
string name = prop.Name;
SolidColorBrush brush = new SolidColorBrush((Color)(prop.GetValue(null, null)));
this.Add(new FontColor(name, brush));
}
}
}
These are the helper classes
public static class Ext_CollectionExtensions
{
public static ObservableCollection<DataRow> ToObservableCollection(this DataTable coll)
{
var c = new ObservableCollection<DataRow>();
foreach (DataRow e in coll.Rows)
c.Add(e);
return c;
}
public static ObservableCollection<T> ToObservableCollection<T>(this List<T> ListOfT)
{
ObservableCollection<T> returned = new ObservableCollection<T>();
foreach (var itm in ListOfT)
{
returned.Add(itm);
}
return returned;
}
}
public static class Ext_Object
{
public static T Coalesce<T>(this T obj, params T[] args)
{
if (obj != null || Convert.IsDBNull(obj))
return obj;
foreach (T arg in args)
{
if (arg != null || Convert.IsDBNull(obj))
return arg;
}
return default(T);
}
}
I have modified the code to be more concise. I did not write the original which I did not feel help in the solving of this problem because it was overly complex and verbose.
The answer after much fartting about is
this.SelectedColor = this.FontColors.Where(a => a.Name == "Black").FirstOrDefault();
All because the selection is the selection of a value in the supplying collection and not setting a value to display.
I am developing an application to allow a user to enter their employee details of their company into a database. So far I am experimenting with WPF and trying to implement MVVM within my application while using Entity Framework.
I'm creating a Master-Detail application, and have been researching into how to achieve this using MVVM, as I'm very much new to it all.
One of the ways in which I have tried is by creating a property within my View-Model called SelectedEmployee and then binding it to a List View in my xaml, like so;
public Employee _SelectedEmployee;
public Employee SelectedEmployee
{
get
{
return _SelectedEmployee;
}
set
{
if (_SelectedEmployee == value)
return;
_SelectedEmployee = value;
OnPropertyChanged("SelectedEmployee");
}
}
<ListView HorizontalAlignment="Left" Name="listview" VerticalAlignment="Bottom" ScrollViewer.HorizontalScrollBarVisibility="Visible"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding LoadEmployee}" SelectionMode="Single" SelectedItem="{Binding SelectedEmployee, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="150" Grid.Row="1">
I then have a method that allows the user to update a SelectedItem within the List View. But this is where the problem occurs. When I select an item from the List View, it only updates the first row from the database and not the row I wanted to select.
Here's the method;
public void UpdateEmployee(Employee emp)
{
using (DBEntities context = new DBEntities())
{
emp = context.Employees.Where(e => e.EmployeeID == SelectedEmployee.EmployeeID).FirstOrDefault();
emp.Title = Title;
emp.FirstName = FirstName;
emp.Surname = Surname;
emp.Position = Position;
emp.DateOfBirth = DateOfBirth;
emp.Address = Address;
emp.Country = Country;
emp.Postcode = Postcode;
emp.PhoneNumber = PhoneNumber;
emp.MobileNumber = MobileNumber;
emp.FaxNumber = FaxNumber;
emp.Email = Email;
emp.NINumber = NINumber;
emp.ChargableResource = ChargableResource;
emp.ChargeOutRate = ChargeOutRate;
emp.TimeSheetRequired = TimeSheetRequired;
emp.WorkShift = WorkShift;
emp.BenefitsProvided = BenefitsProvided;
context.Employees.ApplyCurrentValues(emp);
context.SaveChanges();
}
}
I have bound my properties within my view model to the text-boxes within my xaml and then implementing OnPropertyChanged.
I am also using Commands to limit the amount of code-behind as its important for testability and maintainability.
Here is the command method to update;
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute);
}
return showUpdateCommand;
}
}
private bool UpdateFormCanExecute()
{
return !string.IsNullOrEmpty(FirstName) ...
}
private void UpdateFormExecute()
{
UpdateOrganisationTypeDetail();
}
As I am new to MVVM, I'm not quite sure what I am doing wrong so would appreciate some input please :).
Then perhaps the problem is with your updating. I don't really understand what your trying to do with listview, but since it's not working in a simple datagrid this might help. it's not so much an answer as something that I wrote to find your error that happens not to contain your error. please do comment if anything is awry.
public partial class MainWindow : Window
{
private static MainViewModel _mainViewModel = new MainViewModel();
public static ObservableCollection<Employee> staff = new ObservableCollection<Employee>();
public MainWindow()
{
InitializeComponent();
this.DataContext = _mainViewModel;
dataGrid1.DataContext = staff;
listview.DataContext = staff;
Employee Employee1 = new Employee();
Employee1.name = "Jeff";
staff.Add(Employee1);
Employee Employee2 = new Employee();
Employee2.name = "Jefferson";
staff.Add(Employee2);
Employee2.name = "Tim";
}
}
public class Employee
{
public string name { get; set; }
}
public class MainViewModel : INotifyPropertyChanged
{
private string _SelectedEmployee;
public string SelectedEmployee
{
get { return _SelectedEmployee; }
set
{
_SelectedEmployee = value;
NotifyPropertyChanged("SelectedEmployee");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}