In my application I'm using a usercontrol called "ChannelControls" which I instance 6 times, on the mainwindow.
public partial class ChannelControls : UserControl
{
CMiXData cmixdata = CMiXData.Instance;
public ChannelControls()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty ChannelSpriteCountProperty =
DependencyProperty.Register("ChannelSpriteCount", typeof(string), typeof(ChannelControls), new PropertyMetadata("1"));
[Bindable(true)]
public string ChannelSpriteCount
{
get { return (string)this.GetValue(ChannelSpriteCountProperty); }
set { this.SetValue(ChannelSpriteCountProperty, value); }
}
I'm making using a custom class called cmixdata to hold all the data for my application (it will contains different properties with List of string, double etc...). The ChannelControls will contains many sliders, button and other usercontrols but at the moment I'm trying to bind just one of them.
Here is one part of this custom class that will hold the data, it has a private constructor as I need to access it from anywhere :
[Serializable]
public class CMiXData : INotifyPropertyChanged
{
private static CMiXData _instance = null;
public static CMiXData Instance
{
get
{
if (_instance == null)
{
_instance = new CMiXData();
}
return _instance;
}
}
private CMiXData() { } //prevent instantiation from outside the class
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
MessageBox.Show(propertyName);
}
private List<string> _SpriteCount = new List<string>(new string[] {"1", "1", "1", "1", "1", "1"});
public List<string> SpriteCount
{
get { return _SpriteCount; }
set
{
if(_SpriteCount != value)
{
_SpriteCount = value;
RaisePropertyChanged("SpriteCount");
}
}
}
And here is how I'm trying to bind the channelcontrol property ChannelSpriteCount to my singleton class : cmixdata.
<CMiX:ChannelControls x:Name="Layer0" Tag="0" Visibility="Visible" ChannelSpriteCount="{Binding SpriteCount[0], Mode=TwoWay}"/>
On the main usercontrol, which ChannelControls is instanced, the datacontext is set this way :
public partial class CMiX_UI : UserControl
{
BeatSystem beatsystem = new BeatSystem();
CMiXData cmixdata = CMiXData.Instance;
public CMiX_UI()
{
InitializeComponent();
this.DataContext = cmixdata;
}
And on the xaml side :
<UserControl
x:Class="CMiX.CMiX_UI"
DataContext="{x:Static CMiX:CMiXData.Instance}"
But for some reason the property in cmixdata is not updated and always has the default value...
A UserControl should never have an "own" instance of a view model. Instead, it should have dependency properties that are bound to properties of an "external" view model.
Your ChannelsControl would declare a property like this (where I suppose that string is not an appropriate type for a count):
public partial class ChannelsControl : UserControl
{
public static readonly DependencyProperty SpriteCountProperty =
DependencyProperty.Register(
nameof(SpriteCount), typeof(string), typeof(ChannelsControl));
public string SpriteCount
{
get { return (string)GetValue(SpriteCountProperty); }
set { SetValue(SpriteCountProperty, value); }
}
...
}
In ChannelsControl's XAML, you would bind it like this:
<CMiX:Counter Count="{Binding SpriteCount,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
You would now use your UserControl like shown below, where you bind the Count property to a view model in the DataContext like this:
<Window.DataContext>
<local:CMiXData />
</Window.DataContext>
...
<local:ChannelsControl SpriteCount="{Binding SpriteCount[0]}" ... />
You may now also use ChannelsControl in the ItemTemplate of an ItemsControl like this:
<ItemsControl ItemsSource="{Binding SpriteCount}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelsControl SpriteCount="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
EDIT: Set the Window's DataContext to your view model singleton instance like this:
<Window ... DataContext="{x:Static local:CMiXData.Instance}" >
Or in code behind, in the MainWindow constructor:
DataContext = CMiXData.Instance;
Related
Problem: When attempting to bind an Observable Collection to my user control, it is always showing null at runtime.
Description: I have a user control as described below. The goal is to create a button to cycles through an array of images with every click. However, when I run this, ImageCollection is always null, regardless of how I setup the binding on the implementation side. I'm at a loss for why this is. The code is as follows:
XAML:
<UserControl x:Class="kTrack.ToggleImage"
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:kTrack"
mc:Ignorable="d"
d:DesignHeight="{Binding ImageHeight}" d:DesignWidth="{Binding ImageWidth}">
<Grid x:Name="LayoutRoot">
<Button Click="ToggleImage_Click" Height="{Binding ImageHeight}" Width="{Binding ImageWidth}">
<Image Source="{Binding ActiveImage}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ToolTip="{Binding ToolTip}" />
</Button>
</Grid>
</UserControl>
Code-Behind (Important Bits)
public partial class ToggleImage : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ImageCollectionProperty = DependencyProperty.Register("ImageCollection", typeof(ObservableCollection<string>), typeof(ToggleImage), new PropertyMetadata(null));
...
public ObservableCollection<String> ImageCollection
{
get => (ObservableCollection<String>)GetValue(ImageCollectionProperty);
set => SetValue(ImageCollectionProperty, value);
}
private ImageSource _activeImage;
public ImageSource ActiveImage
{
get => _activeImage;
set
{
_activeImage = value;
OnPropertyChanged();
}
}
private int _currentImageIndex;
public int CurrentImageIndex
{
get => _currentImageIndex;
set
{
_currentImageIndex = value;
OnPropertyChanged();
}
}
// Constructor
public ToggleImage()
{
ImageCollection = new ObservableCollection<String>();
InitializeComponent();
LayoutRoot.DataContext = this;
CurrentImageIndex = 0;
if (ImageCollection.Count > 0)
{
ActiveImage = SetCurrentImage(CurrentImageIndex);
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Implementation:
<local:ToggleImage ImageHeight="32" ImageWidth="32"
ImageCollection="{Binding Images, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
Code-Behind of Implementation:
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
}
}
for one, you should set DataContext in Window.
for two, Binding work with properties, not fields: change Images field to property.
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images { get; } = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}
My issue is my OnMatrixPropertyChanged method never gets called. The label, which is bound to the same property, does update so I know binding is happening on the Matrix property.
I have a UserControl that I want to add a DependencyProperty to in order that it can be bound to. My MainWindow looks like this:
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<StackPanel>
<Button
Command="{Binding LoadMatrixCommand}"
Content="Load"
Width="150">
</Button>
<Label
Content="{Binding Matrix.Title}">
</Label>
<controls:MatrixView
Matrix="{Binding Path=Matrix, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</controls:MatrixView>
</StackPanel>
In my MatrixView UserControl code-behind I have the DependencyProperty set as such:
public partial class MatrixView : UserControl
{
public static readonly DependencyProperty MatrixProperty =
DependencyProperty.Register(nameof(Matrix), typeof(Matrix), typeof(MatrixView), new PropertyMetadata(default(Matrix), OnMatrixPropertyChanged));
private static void OnMatrixPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Do Something
}
public Matrix Matrix
{
get => (Matrix)GetValue(MatrixProperty);
set => SetValue(MatrixProperty, value);
}
public MatrixView()
{
InitializeComponent();
}
}
I must be missing something very obvious...
EDIT #1: View Models
public class MatrixViewModel : ViewModelBase
{
public MatrixViewModel()
{
}
}
public class MainWindowViewModel : ViewModelBase
{
private IMatrixService _matrixService;
private Matrix _matrix;
public Matrix Matrix
{
get => _matrix;
set
{
_matrix = value;
base.RaisePropertyChanged();
}
}
public ICommand LoadMatrixCommand { get; private set; }
public MainWindowViewModel()
{
LoadMatrixCommand = new RelayCommand(LoadMatrix);
_matrixService = new MatrixService();
}
private void LoadMatrix()
{
var matrixResult = _matrixService.Get(1);
if (matrixResult.Ok)
{
Matrix = matrixResult.Value;
}
}
}
There certainly is something like
<UserControl.DataContext>
<local:MatrixViewModel/>
</UserControl.DataContext>
in the XAML of your UserControl. Remove that, because it prevents that a Binding like
<controls:MatrixView Matrix="{Binding Matrix}" />
looks up the Matrix property in the correct view model instance, i.e. the one inherited from the MainWindow.
UserControls with bindable (i.e. dependency) properties should never set their own DataContext, because doing so breaks any DataContext based bindings of these properties.
My problem is quite simple, I want to have a MainView which in turn will have multiple Views which are dynamic and intractable, like in the diagram below:
But to do this you need multiple ViewModels, and I do not know how to organise them.
My original Idea is to have a MainViewModel, within which I will create properties that will return all my ChildViewModels as shown below, but It seems unprofessional to me and a bad practice.
public class MainViewModel : BaseViewModel
{
private EditPropertiesViewModel _editPropertiesViewModel;
public EditPropertiesViewModel EditPropertiesViewModel
{
get { return _editPropertiesViewModel; }
set
{
_editPropertiesViewModel = value;
base.OnPropertyChanged();
}
}
private UsersDetailsViewModel _usersDetailsViewModel;
public UsersDetailsViewModel UsersDetailViewModel
{
get { return _usersDetailsViewModel; }
set
{
_usersDetailsViewModel = value;
base.OnPropertyChanged();
}
}
//etc. etc..
}
Then from My MainView, I would set the Datacontext to the MainViewModel
Please help me I have no idea what to do, I am totally paused right now.
If you wish to achieve this without PRISM you can make use of ContentControl. For every region you create ContentControl and for every ContentControl you create its ViewModel property. Then you manipulate selected ViewModel associated with ContentControl and ContentControl adjusts view based on type of ViewModel assigned. For clarification take a look
XAML:
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type viewModel:SubArticleViewModel}">
<view:SubArticleView/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding ArticleViewModel}"/>
C#
class BaseViewModel : INotifyPropertyChanged
{
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
class MainViewModel
{
public BaseViewModel ArticleViewModel { get; set; }
}
class SubArticleViewModel : BaseViewModel
{
}
Whenever you assign
ArticleViewModel = new SubArticleViewModel();
DataTemplate defined as resource will be placed as Content of Control.
Above way out creates a lots of work and is more vulnerable for omission. PRISM would be a better choice anyway.
Create AppViewModel class with static ctor like this:
class AppViewModel : BaseViewModel
{
static AppViewModel()
{
_AppModel = new AppViewModel();
}
private static AppViewModel _AppModel;
public static AppViewModel Current
{
get { return _AppModel; }
}
private AppViewModel()
{
//Initialize view models here
MainPageModel = new MainPageViewModel();
}
//VIEW MODELS
public MainPageViewModel MainPageModel { get; private set; }
}
Create BaseViewModel class. All of your VM's should be inherited from it:
class BaseViewModel //implement INotifyPropertyChanged if needed
{
public AppViewModel AppModel
{
get { return AppViewModel.Current; }
}
}
So now you can create UserControl called "MainView":
public partial class MainView : UserControl
{
public MainView()
{
InitializeComponent();
//Prevent view updating in Designer
if (DesignerProperties.GetIsInDesignMode(this))
{
return;
}
var mainVM = AppViewModel.Current.MainPageModel;
DataContext = mainVM;
}
}
In the MainWindow.xaml:
<Window x:Class="MVVM_Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:MVVM_Test.Views"
Title="MainWindow" Height="350" Width="525">
<views:MainView />
</Window>
I have created blank C#/XAML Windows 8 application. Add simple XAML code:
<Page
x:Class="Blank.MainPage"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel
Margin="0,150"
HorizontalAlignment="Center">
<TextBlock
x:Name="xTitle"
Text="{Binding Title, Mode=TwoWay}"/>
<Button Content="Click me!" Click="OnClick" />
</StackPanel>
</Grid>
</Page>
And the simple code in C# part:
public sealed partial class MainPage
{
private readonly ViewModel m_viewModel;
public MainPage()
{
InitializeComponent();
m_viewModel = new ViewModel
{
Title = "Test1"
};
DataContext = m_viewModel;
}
private void OnClick(object sender, RoutedEventArgs e)
{
m_viewModel.Title = "Test2";
}
}
Now I want to implement ViewModel. I have two way:
Use Dependency Property
Implement INotifyPropertyChanged
For first approach it is:
public class ViewModel : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string)
, typeof(ViewModel)
, new PropertyMetadata(string.Empty));
}
For second it is:
public class ViewModel : INotifyPropertyChanged
{
private string m_title;
public string Title
{
get
{
return m_title;
}
set
{
m_title = value;
OnPropertyChanged("Title");
}
}
protected void OnPropertyChanged(string name)
{
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
I prefer the first way, because it allows use coerce (Silverlight for web and for WP7 doesn't have coerce functionality.. WinRT too.. but I'm still looking and hope) and looks more natural for me. But unfortunately, it works as OneTime for the first approach.
Could anybody explain to me why MS abandon using Dependency Property for implementing view model?
You should not be using a DependencyProperty in your ViewModel - you should only use them in your controls. You will never want to bind one ViewModel to another, also ViewModels do not need to persist their values nor provide default values, nor provide property metadata.
You should only use INotifyPropertyChanged in your ViewModels.
I cannot get any display from my observable collection in a custom object bound to a ListBox. This works fine when I have a string collection in my view model, but no names display when I try to access the property through a custom object. I am not receiving any errors in the output window.
Here is my code:
Custom Object
public class TestObject
{
public ObservableCollection<string> List { get; set; }
public static TestObject GetList()
{
string[] list = new string[] { "Bob", "Bill" };
return new TestObject
{
List = new ObservableCollection<string>(list)
};
}
}
Xaml
<Window x:Class="TestWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox Height="100" HorizontalAlignment="Left" Margin="120,61,0,0" Name="listBox1" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Path=TObj.List}" />
</Grid>
Xaml.cs
public partial class MainWindow : Window
{
private ModelMainWindow model;
public MainWindow()
{
InitializeComponent();
model = new ModelMainWindow();
this.DataContext = model;
this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
public void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
this.model.Refresh();
}
}
ViewModel
public class ModelMainWindow : INotifyPropertyChanged
{
private TestObject tObj;
public event PropertyChangedEventHandler PropertyChanged;
public TestObject TObj
{
get
{
return this.tObj;
}
set
{
this.tObj = value;
this.Notify("Names");
}
}
public void Notify(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public void Refresh()
{
this.TObj = TestObject.GetList();
}
}
Can't bind to private properties. Also the change notification targets the wrong property, change "Names" to "TObj". (Also i would recommend making the List property get-only (backed by a readonly field), or implementing INoptifyPropertyChanged so the changes cannot get lost)
Your List is private. Make it a public property otherwise WPF can't see it.