I'm learning on how to implement inversion of control in the wpf application through the below link
https://www.codeguru.com/columns/experts/implementing-the-inversion-of-control-pattern-in-c.htm
The intent of the application is to load the data from table to combobox. The code works fine without any issue. So as a part of learning ooad design principle I thought below is the perfect case on which the highlevel module (Viewmodel) depend upon lowerlevel module (populatetab2combobox). I believe here we can apply dependency inversion. on reading online most of them started with inversion of control as a first step towards implementing Dependency inversion.
I just want to iterate that I don't have prior experience in oops principle. I might be wrong in my assumption. Please correct me if I'm wrong.
Window.xaml :
<ComboBox ItemsSource="{Binding populatecombobox.modeltogetusername}" Width="155" Margin="0,-20,-180,137">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding username}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
View Model :
Code without implementing inversion of control(This works fine without any issue):
//Class to populate the combobox
public class populatetab2combobox
{
public ObservableCollection<comboboxdata> modeltogetusername { get; set; }
public void getdatausinglinq()
{
using (Operations_Productivity_ToolEntities context = new Operations_Productivity_ToolEntities())
{
var a1 = from t1 in context.Test_ImportedAuditdata
select t1;
if (modeltogetusername == null)
modeltogetusername = new ObservableCollection<comboboxdata>();
foreach (var a in a1.GroupBy(x => x.username).Select(x => x.FirstOrDefault()))
{
modeltogetusername.Add(new comboboxdata
{
username = a.username.ToString()
});
}
}
}
}
public class ViewModel: INotifyPropertyChanged {
/** You can see that I'm calling viewModel class is depended with
populatetab2combobox. I believe this is the perfect case for implementing
Inversion of control **/
populatetab2combobox_populatecombobox = new populatetab2combobox();
private PopulateDatagrid _populatedatagridwithobservablecollection = new PopulateDatagrid();
private Loadfileintodatabase loaddata = new Loadfileintodatabase();
public PopulateDatagrid Populatedatagridwithobservablecollection {
get {
return _populatedatagridwithobservablecollection;
}
set {
if (value != _populatedatagridwithobservablecollection) {
_populatedatagridwithobservablecollection = value;
OnPropertyChanged("Populatedatagridwithobservablecollection");
}
}
}
DataModel dm = new DataModel();
public ViewModel() {
_populatecombobox.getdatausinglinq();
DoSomeThingCmd = new RelayCommand(o = >search());
_populatedatagridwithobservablecollection.getdatausinglinq();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Code with implementation of inversion of control(The issue is combobox is empty when i run when IOC implemented):
public interface INofificationAction
{
void getdatausinglinq();
}
public class populatetab2combobox : INofificationAction
{
public ObservableCollection<comboboxdata> modeltogetusername { get; set; }
public void getdatausinglinq()
{
using (Operations_Productivity_ToolEntities context = new Operations_Productivity_ToolEntities())
{
var a1 = from t1 in context.Test_ImportedAuditdata
select t1;
if (modeltogetusername == null)
modeltogetusername = new ObservableCollection<comboboxdata>();
foreach (var a in a1.GroupBy(x => x.username).Select(x => x.FirstOrDefault()))
{
modeltogetusername.Add(new comboboxdata
{
username = a.username.ToString()
});
}
}
}
}
public class implementingabstraction
{
INofificationAction _an;
public implementingabstraction(INofificationAction action)
{
this._an = action;
}
public void getdatausinglinq()
{
_an.getdatausinglinq();
}
}
public class ViewModel: INotifyPropertyChanged
{
INofificationAction getdata123 = new populatetab2combobox();
populatetab2combobox _populatecombobox = new populatetab2combobox();
public populatetab2combobox populatecombobox {
get {
return _populatecombobox;
}
set {
if (value != _populatecombobox) {
_populatecombobox = value;
OnPropertyChanged("populatetab2combobox");
}
}
}
public RelayCommand DoSomeThingCmd {
get;
set;
}
DataModel dm = new DataModel();
public ViewModel() {
implementingabstraction abs = new implementingabstraction(getdata123);
abs.getdatausinglinq();
DoSomeThingCmd = new RelayCommand(o = >search());
_populatedatagridwithobservablecollection.getdatausinglinq();
}
}
questions:
1) since I'm very new to oops based approach.Whether the above usecase is correct.
2) The above code is not working correctly. Can you please point out the mistake in my approach.
Related
I am struggling to pass an object from one view to another using .Net Maui and a UI independent MVVM pattern.
Most or all MAUI examples I have found to date use and promote MVVM. They also tend to store the View-Models within the UI project and utilize MAUI App Shell navigation directly. This in my opinion and understanding potentially omits some of the benefits of MVVM as View Models would not work with other UI projects.
I have attempted to create a working example* with the View Models, Models and Services being in a separate UI independent project and referenced by the UI project(s) which contain the Views. Sample Project Published to GitHub I would like to update the project with a solution so it can be a working example.
I am specifically stuck where when clicking on a single object from a list of objects it opens a detail view but the object is not passed successfully. In this case my example utilizes a list of customer orders and I am attempting to open the order in a detail view when clicking on an order from the order list.
My problem lies somewhere between the XAML binding to the GoToOrdersCommand, how the command is implemented, and Order object passed to the Order View-Model.
Why do I have both a DelegateCommand and RelayCommand, I am not sure. Different examples I have viewed have used these names, I am not sure if one name is correct. Or if they should be combined into one class. I believe the only significant difference for this example is that RelayCommand accepts an object as a parameter.
Orders View-Model
namespace Orders.Common.ViewModel
{
public class OrdersViewModel : ViewModelBase
{
public ObservableCollection<Order> Orders { get; } = new();
private readonly OrderDataProvider _orderDataProvider;
private IOrderNavigation _navigationService;
public RelayCommand<Order> GoToOrdersCommand { get; }
private Order _order;
public OrdersViewModel(IOrderNavigation navigationService)
{
Orders = new ObservableCollection<Order>();
_orderDataProvider = new OrderDataProvider();
_navigationService = navigationService;
_order = new Order();
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(_order));
Load();
}
public void Load()
{
var orders = _orderDataProvider.GetAllOrders();
Orders.Clear();
foreach (var order in orders)
Orders.Add(order);
}
public void OrderDetails(Order order)
{
if (order == null)
return;
_navigationService.NavigateToOrderAsync(order);
}
}
}
Order View-Model
public class OrderViewModel : ViewModelBase
{
private Order _order;
public Order Order
{
get => _order;
set
{
if (_order != value)
{
_order = value;
RaisePropertyChanged();
}
}
}
private OrderDataProvider _orderDataProvider = new OrderDataProvider();
public DelegateCommand SaveCommand { get; }
public ICommand SaveOrder { get; set; }
public OrderViewModel()
{
_order = new Order() { OrderID = -1, Customer = "", OrderDate = DateTime.Now };
SaveCommand = new DelegateCommand(Save, () => CanSave);
}
public OrderViewModel(Order order)
{
_order = order;
SaveCommand = new DelegateCommand(Save, () => CanSave);
}
public bool CanSave => !string.IsNullOrEmpty(CustomerName) && CustomerName.Length >= 3;
public void Save()
{
throw new NotImplementedException();
}
public int OrderID
{
get => _order.OrderID;
set
{
if (_order.OrderID != value)
{
_order.OrderID = value;
RaisePropertyChanged();
}
}
}
public string CustomerName
{
get => _order.Customer;
set
{
if (_order.Customer != value)
{
_order.Customer = value;
RaisePropertyChanged();
RaisePropertyChanged(nameof(CanSave));
SaveCommand.RaiseCanExecuteChanged();
}
}
}
public DateTime OrderDate
{
get => _order.OrderDate;
set
{
if (_order.OrderDate != value)
{
_order.OrderDate = value;
RaisePropertyChanged();
}
}
}
}
Inherited UI Specific Navigation
namespace TestOrders.Navigate
{
public class OrderNavigationService : IOrderNavigation
{
public void NavigateToOrdersAsync()
{
Shell.Current.GoToAsync(nameof(OrdersPage));
}
public void NavigateToOrderAsync(Order order)
{
Shell.Current.GoToAsync(nameof(OrderPage), true, new Dictionary<string, object> { { "Order", order } });
}
}
}
RelayCommand
namespace Orders.Common.ViewModel.Command
{
public class RelayCommand<T> : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
}
Orders View
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestOrders.Pages.OrdersPage"
Title="OrdersPage"
xmlns:model="clr-namespace:Orders.Common.Model;assembly=Orders.Common"
xmlns:viewmodel="clr-namespace:Orders.Common.ViewModel;assembly=Orders.Common"
x:DataType="viewmodel:OrdersViewModel">
<VerticalStackLayout>
<Label Text="Orders" HorizontalOptions="Center"/>
<CollectionView
ItemsSource="{Binding Orders}"
SelectionMode="None">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="model:Order">
<Grid Padding="10">
<Frame HeightRequest="70">
<Frame.GestureRecognizers>
<TapGestureRecognizer
Command="{Binding Source={RelativeSource AncestorType={x:Type viewmodel:OrdersViewModel}}, Path=GoToOrdersCommand}"
CommandParameter="{Binding .}"/>
</Frame.GestureRecognizers>
<Grid Padding="0" ColumnDefinitions="20,*">
<Label Text="{Binding OrderID}" Grid.Column="0"/>
<Label Text="{Binding Customer}" Grid.Column="1"/>
</Grid>
</Frame>
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
</ContentPage>
*Being new to .Net Maui, XAML based UI's, MVVM patterns and inexperienced with additional topics such as interfaces and delegates there are likely best practices or coding conventions I have missed. I am also trying to avoid utilizing the MVVM community toolkit or other toolkits until I have a better understanding of MVVM and how the interfaces and commands function.
I believe that this
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(_order));
should be
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails(order));
_order is a new instance that you create on the preceding line, NOT the instance being passed in from the view's binding
Change to OrdersViewModel Constructor
public OrdersViewModel(IOrderNavigation navigationService)
{
Orders = new ObservableCollection<Order>();
_orderDataProvider = new OrderDataProvider();
_navigationService = navigationService;
GoToOrdersCommand = new RelayCommand<Order>((order) => OrderDetails((Order)order));
GoToNewOrderCommand = new DelegateCommand(NewOrder); // Additional navigate without object
Load();
}
Additional Command method to navigate to a New/Empty Order Detail for reference added to the OrdersViewModel
private void NewOrder()
{
_navigationService.NavigateToOrderAsync();
}
Updated Order Navigation
public class OrderNavigationService : IOrderNavigation
{
public void NavigateToOrdersAsync()
{
Shell.Current.GoToAsync(nameof(OrdersPage));
}
public void NavigateToOrderAsync(Order order)
{
Shell.Current.GoToAsync(nameof(OrderPage), true, new Dictionary<string, object> { { "Order", order } });
}
public void NavigateToOrderAsync()
{
Shell.Current.GoToAsync(nameof(OrderPage));
}
}
OrderPage View Code Behind File
Added QueryProperty, a field and method to create an OrderViewModel if an Order object is provided.
[QueryProperty(nameof(order), "Order")]
public partial class OrderPage : ContentPage
{
public Order order
{
set
{
Load(value);
}
}
public OrderPage()
{
InitializeComponent();
BindingContext = new OrderViewModel();
}
private void Load(Order order)
{
if (order != null)
BindingContext = new OrderViewModel(order);
}
}
I have doubts regarding my implementation of observer pattern, but with complete separation of concerns.
Example below is not a real life code, but just an example of idea how I want to do it.
In my solution I have two project layers:
Desktop layer (views, view models, models)
Service library layer (with observers)
My view model is a subject subscribing the observers.
Code in VM:
interface ISubject
{
void Subscribe(IObserverService observer);
void Unsubscribe(IObserverService observer);
void Notify();
}
public class MainWindowViewModel : ViewModelBase, ISubject
{
private readonly IObserverService _observer1;
private readonly IObserverService _observer2;
private ArrayList _observers;
public MainWindowViewModel(
IObserver1 observer1,
IObserver2 observer2)
{
_observer1 = observer1;
_observer2 = observer2;
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
private void InitProgram()
{
_observers = new ArrayList();
_observers.Add(_observer1);
_observers.Add(_observer2);
}
public List<IObserverService> Observers { get; set; }
private void OnSwitchCommand(object obj)
{
if (Jeden == true)
{
UiModel = _controlsService.SwitchOff();
}
else
{
UiModel = _controlsService.SwitchOn();
}
}
private void OnObserverCommand(object obj)
{
SomeValue++;
}
public void Subscribe(IObserverService observer)
{
Observers.Add(observer);
}
public void Unsubscribe(IObserverService observer)
{
Observers.Remove(observer);
}
public void Notify()
{
Observers.ForEach(x => x.Update(SomeValue));
}
public ICommand ObserverCommand { get; private set; }
private int _someValue;
public int SomeValue
{
get => _someValue;
set
{
_someValue = value;
InformObservers();
}
}
private void InformObservers()
{
foreach (IObserverService x in _observers)
{
x.Update(SomeValue);
}
}
}
And my observer in service layer is very simple. After Update call from the subject is displaying new MessageBox:
public interface IObserverService
{
void Update(int someValue);
}
public class Observer1 : IObserver1, IObserverService
{
public string ObserverName { get; private set; }
public Observer1(string name)
{
this.ObserverName = name;
}
public void Update(int someValue)
{
MessageBox.Show("New value: " + someValue.ToString() + " for " + ObserverName);
}
}
Observer2 is same as above.
Right now I have doubts how my constructor supposed to look like, if I want to create a new observer with a name parameter, for example: new Observer1("name1") in this case, keeping separation, should my subject's ctor look like:
public MainWindowViewModel()
{
_observerService = observerService;
IObserverService observer1 = new ObserverService("name1");
IObserverService observer2 = new ObserverService("name2");
SwitchCommnad = new DelegateCommand(OnSwitchCommand);
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
Is it correct approach? Is it going to be testable? Or I have to inject IObserverService somehow?
If you want to test your VM, follow IoC and don't create your ObserverServices inside it but as you say, inject IObserverService; therefore you'll be able to mock the services and test your VM without needing the whole service behavior.
I may suggest you to use Autofac or even Ninject. There are plenty of DI frameworks so look for the one that adjust to what you are looking for.
it makes sense, that MainWindowViewModel will receive some external observers via constructor:
public MainWindowViewModel(IObserver1 observer1, IObserver2 observer2)
{
_observer1 = observer1;
_observer2 = observer2;
ObserverCommand = new DelegateCommand(OnObserverCommand);
InitProgram();
}
when you create an instance of MainWindowViewModel (I assume, it will be used for MainWindowView DataContext), you will pass some real observers:
IObserverService observer1 = new ObserverService("name1");
IObserverService observer2 = new ObserverService("name2");
var vm = new MainWindowViewModel(observer1, observer2);
mainWindow.DataContext = vm;
no need for DI container here if dependencies can be resolved statically
similarly, for test you can have some TestObserverService (or IObserverService mock):
IObserverService observer1 = new TestObserverService("name1");
IObserverService observer2 = new TestObserverService("name2");
var vm = new MainWindowViewModel(observer1, observer2);
MainWindowViewModel might create some IObserverServices, if it has properties worth observing from other objects in the application (e.g. related view models)
The main idea what I am trying to do - to have one VM, which has a lot of other VMs.
The problem is to organize data transportation.
Main VM is connected with a template and other VMs have their own templates.
I use a navigator to change VMs and template selector to change templates.
Navigator:
public class NavigationController : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private List<ViewModelBase> _viewModels;
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel {
get { return _currentViewModel; }
set { _currentViewModel = value; OnPropertyChanged(nameof(CurrentViewModel)); }
}
private List<ViewModelBase> _legViewModels;
private ViewModelBase _legViewModel;
public ViewModelBase LegViewModel
{
get { return _legViewModel; }
set { _legViewModel = value; OnPropertyChanged(nameof(LegViewModel)); }
}
public NavigationController()
{
_viewModels = new List<ViewModelBase>
{
new ViewModelLogin(this),
new ViewModelPhysicalOverview(this),
...list of VMs...
};
_currentViewModel = _viewModels.First();
_legViewModels = new List<ViewModelBase>
{
new SFSViewModel(this),
new BPVHipViewModel(this)
};
_legViewModel = _legViewModels.First();
}
public void NavigateTo<T>()
{
var target = _viewModels.FirstOrDefault(e => e.GetType() == typeof(T));
if (target != null)
CurrentViewModel = target;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
//если PropertyChanged не нулевое - оно будет разбужено
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
My main VM:
public BPVHipViewModel LeftBPVHip { get; protected set; }
public SFSViewModel LeftSFS { get; protected set; }
public BPVHipViewModel RightBPVHip { get; protected set; }
public SFSViewModel RightSFS { get; protected set; }
public ViewModelAddPhysical(NavigationController controller) : base(controller)
{
LeftBPVHip = new BPVHipViewModel(Controller);
RightBPVHip = new BPVHipViewModel(Controller);
LeftSFS = new SFSViewModel(Controller);
RightSFS = new SFSViewModel(Controller);
Controller = controller;
base.HasNavigation = false;
ToRightBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftBPVHipCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftBPVHip;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToLeftSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = LeftSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
ToRightSFSCommand = new DelegateCommand(
() =>
{
Controller.LegViewModel = RightSFS;
Controller.NavigateTo<LegPartViewModel>();
}
);
}
So before I go to another VM and change my screen, I do
Controller.LegViewModel = RightSFS;
and I thought if I change something in RightSFS - it will keep changes after returning to main VM. But I guess it doesn't work like this.
In children I have:
private bool _isEmpty = true;
public bool IsEmpty {
get
{
return _isEmpty;
}
protected set {
_isEmpty = value;
OnPropertyChanged("IsEmpty");
}
}
public string ButtonText
{
get
{
if (!IsEmpty) return "Edit";
else return "Fill";
}
}
And a fn that fires before I return to parent screen:
SaveCommand = new DelegateCommand(
() =>
{
IsEmpty = false;
Controller.NavigateTo<ViewModelAddPhysical>();
}
);
so I want a button from main template to show if we already have visited child screen, in this case I want "Edit" text. But it returns "Fill" all the time, 'cause IsEmpty doesn't change from true to false for him and I don't understand how to fix it. Please help.
For me, you are trying to invent a wheel of your own. It's done, multiple times. Every MVVM framework outthere has built-in navigation.
Take a look at ReactiveUI (great framework) samples, they are doing exactly what you need.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ninject;
using ReactiveUI.Samples.Routing.Views;
using Splat;
namespace ReactiveUI.Samples.Routing.ViewModels
{
/* COOLSTUFF: What is the AppBootstrapper?
*
* The AppBootstrapper is like a ViewModel for the WPF Application class.
* Since Application isn't very testable (just like Window / UserControl),
* we want to create a class we can test. Since our application only has
* one "screen" (i.e. a place we present Routed Views), we can also use
* this as our IScreen.
*
* An IScreen is a ViewModel that contains a Router - practically speaking,
* it usually represents a Window (or the RootFrame of a WinRT app). We
* should technically create a MainWindowViewModel to represent the IScreen,
* but there isn't much benefit to split those up unless you've got multiple
* windows.
*
* AppBootstrapper is a good place to implement a lot of the "global
* variable" type things in your application. It's also the place where
* you should configure your IoC container. And finally, it's the place
* which decides which View to Navigate to when the application starts.
*/
public class AppBootstrapper : ReactiveObject, IScreen
{
public RoutingState Router { get; private set; }
public AppBootstrapper(IMutableDependencyResolver dependencyResolver = null, RoutingState testRouter = null)
{
Router = testRouter ?? new RoutingState();
dependencyResolver = dependencyResolver ?? Locator.CurrentMutable;
// Bind
RegisterParts(dependencyResolver);
// TODO: This is a good place to set up any other app
// startup tasks, like setting the logging level
LogHost.Default.Level = LogLevel.Debug;
// Navigate to the opening page of the application
// you can set any property of this new VM to transport data
Router.Navigate.Execute(new WelcomeViewModel(this));
}
private void RegisterParts(IMutableDependencyResolver dependencyResolver)
{
dependencyResolver.RegisterConstant(this, typeof(IScreen));
dependencyResolver.Register(() => new WelcomeView(), typeof(IViewFor<WelcomeViewModel>));
}
}
}
I used MessageBus pattern, it was perfect solution for me
class Subscription
{
public object Instance { get; set; }
public Action<object, object> Handler;
}
public class MessageBus
{
#region Singleton
private static readonly MessageBus _instance = new MessageBus();
public static MessageBus Default => _instance;
private MessageBus()
{
}
#endregion
private readonly Dictionary<string, List<Action<object, object>>> _hadlersMap
= new Dictionary<string, List<Action<object, object>>>();
public void Call(string name, object sender, object data)
{
List<Action<object, object>> handlers;
if(!_hadlersMap.TryGetValue(name.ToUpper(), out handlers))
return;
foreach (var handler in handlers)
{
handler?.Invoke(sender,data);
}
}
public void Subscribe(string name, Action<object, object> handler)
{
name = name.ToUpper();
List<Action<object, object>> handlers;
if (!_hadlersMap.TryGetValue(name, out handlers))
{
handlers = new List<Action<object, object>>{ handler };
_hadlersMap.Add(name, handlers);
}
else
{
handlers.Add(handler);
}
}
}
So I have never seem things be done this way. Have you looked at Windsor. I believe dependency injection and inversion of control could improve the scalability here. As far as suggestions go.
There is a lot of instantiation going on in many different places in the code here. Maybe creating a factory to handle all the new-ing up. IOC would help with that as well. You could place your list of models globally. App.Current.Properties[ "someVm" ] = vmInstance; if you are wanting to save the vm state.
Another way to persist the vm state would of course be to make that vm a singleton ensuring that when called it returns that only instance if already exists or instantiates if not.
Finally, I have persisted vm state upon unloading and reading state from somewhere upon loading. This is common and many default controls do this.
This is the first experience with WPF so please forgive me, I know this is pretty basic but I can't get it to work. I'm simply trying to bind a combobox to an LINQ to EF populated ObservableCollection. When I step through the code I see that the collection is populated, but the combo box doesn't display the contents of the collection.
Here is my ViewModel:
public class MainWindowViewModel : ViewModelBase
{
# region ObservableCollections
private ObservableCollection<Site> _sitescollection;
public ObservableCollection<Site> SiteCollection
{
get { return _sitescollection;}
set {
if (value == _sitescollection) return;
_sitescollection = value;
RaisePropertyChanged("SiteCollection");
}
}
# endregion
public MainWindowViewModel()
{
this.PopulateSites();
}
// Get a listing of sites from the database
public void PopulateSites()
{
using (var context = new Data_Access.SiteConfiguration_Entities())
{
var query = (from s in context.SITE_LOOKUP
select new Site(){Name = s.SITE_NAME, SeqId = s.SITE_SEQ_ID });
SiteCollection = new ObservableCollection<Site>(query.ToList());
}
}
}
My Site Class:
public class Site : INotifyPropertyChanged
{
#region Properties
string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
private int _seqid;
public int SeqId
{
get {
return _seqid;
}
set {
if (_seqid != value)
{
_seqid = value;
RaisePropertyChanged("SeqId");
}
}
}
#endregion
#region Constructors
public Site() { }
public Site(string name, int seqid)
{
this.Name = name;
this.SeqId = seqid;
}
#endregion
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
And my XAML Bindings:
<ComboBox Margin="10"
ItemsSource="{Binding Sites}"
DisplayMemberPath="Name"
SelectedValuePath="SeqId" />
What am I doing wrong? Any assistance would be greatly appreciated.
You bound to path "Sites" but your property name was "SiteCollection".
You bind to properties, so the names have to match. Also make sure your data context is set to your view model object.
My project ViewModel elements are not being found. I'm trying to implement a ViewModel within my WPF Usercontrol. However, the binding isn't working properly and there appears to be no data. I'm trying to create a ViewModel to interact with, putting generic string arrays into, and various other bits of data.
MainWindow.xaml - (Usercontrol declaration)
<panels:FilterLister Grid.Column="0" x:Name="filter1FilterLister" />
MainWindows.cs - (Within the constructor, call to usercontrol
filter1FilterLister.Initialise(typeof(Genre));
FilterListViewModel.cs
public class FilterListViewModel
{
MyEntities context = new MyEntities();
ObservableCollection<string> entries = new ObservableCollection<string>();
public Type SelectedType;
private string p_TypeName;
public string TypeName
{
get { return p_TypeName; }
set {
//p_TypeName = value;
p_TypeName = SelectedType.Name.ToString();
}
}
public FilterListViewModel() { }
public FilterListViewModel(Type selectedType)
{
if (selectedType == typeof(Artist))
{
returnedArray = Artist.ReturnArtistNames(context);
}
// put together ObservableCollection
foreach (var str in returnedArray)
{
entries.Add(str);
}
SelectedType = selectedType;
}
}
FilterLister.xaml
<Label Name="labelToBind" Content="{Binding TypeName}" Grid.Row="0" />
FilterLister.cs
public partial class FilterLister : UserControl
{
FilterListViewModel filterListViewModel;
private MyEntities context;
public FilterLister()
{
InitializeComponent();
context = new MyEntities();
}
public void Initialise(Type objectType)
{
filterListViewModel = new FilterListViewModel(objectType);
this.DataContext = filterListViewModel;
}
}
Based on your code, TypeName is null so you saw nothing on the Label. From your code, I think you want to describe like:
public string TypeName
{
get{ return SelectedType.Name.ToString();}
}
As deryck suggested, you should add INotifyPropertyChanged interface for notification, but it should not affect binding at first time. If you believe ViewModel's data is correct but not populated on UI, you should check DataContext and Binding.
You've missed implement the INotifyPropertyChanged interface in your ViewModel, it's needed to the binded property can send "refresh message" to a UI.
Here is the interface, and how you can implement this:
http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
public class FilterListViewModel : INotifyPropertyChanged
{
MyEntities context = new MyEntities();
ObservableCollection<string> entries = new ObservableCollection<string>();
public Type SelectedType;
private string p_TypeName;
public string TypeName
{
get { return p_TypeName; }
set {
//p_TypeName = value;
p_TypeName = SelectedType.Name.ToString();
NotifyPropertyChanged();
}
}
public FilterListViewModel() { }
public FilterListViewModel(Type selectedType)
{
if (selectedType == typeof(Artist))
{
returnedArray = Artist.ReturnArtistNames(context);
}
// put together ObservableCollection
foreach (var str in returnedArray)
{
entries.Add(str);
}
SelectedType = selectedType;
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}