How to get my ViewModel instantiated - c#

I have an application that works, and now I'm trying to do it with proper MVVM. As I understand it from many months of reading, the LinqToSQL classes (by VS2013) are my model, as is the SQL Server database that it uses. The UI is the view, and I am implementing some ObservableCollections as my view model. So here is the ViewModel:
partial class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
static DataClasses1DataContext _dataDc = new DataClasses1DataContext();
ObservableDocuments _oDoc = new ObservableDocuments(_dataDc);
public ObservableCollection<Document> oDoc
{
get
{
return _oDoc;
}
}
public ICommand LoadData
{
get;
private set;
}
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
In the working version, the DataContext and ObservableDocuments lines were in the MainWindow code . . . so I have moved them. Here is the xaml:
<Window x:Class="LINQ_MVVM_1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:LINQ_MVVM_1.ViewModel"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid AutoGenerateColumns="False" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch"
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
ItemsSource="{Binding oDoc.View}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="docIDColumn" Binding="{Binding DocId}" Header="ID" Width="65"/>
<DataGridTextColumn x:Name="DocumentNumberColumn" Binding="{Binding Number}" Header="Document Number" Width="*"/>
<DataGridTextColumn x:Name="altIDColumn" Binding="{Binding AltID}" Header="Alt" Width="55"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
And here is the code for the binding reference object, a ViewableCollection, with a filterable view property:
public class ViewableCollection<T> : ObservableCollection<T>
{
private ListCollectionView _View;
public ListCollectionView View
{
get
{
if (_View == null)
{
_View = new ListCollectionView(this);
}
return _View;
}
}
}
class ObservableDocuments : ViewableCollection<Document>
{
public ObservableDocuments(DataClasses1DataContext dataDc)
{
foreach (Document doc in dataDc.Documents)
{
this.Add(doc);
}
}
}
Moving the _dataDc and _oDoc into the ViewModel class has resulted in nothing populating the data grid. What am I doing wrong? It seems that getting the ViewModel instantiated is not happening.

This line:
DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Means that you are still looking at the code-behind for your DataContext. In case you aren't aware, the DataContext defines the root object to which all bindings look at the start of their "Path".
So you're right, the VM isn't being instantiated because you never instantiated it.
Instead, remove that line and in your UI constructor write:
DataContext = new MainViewModel();
There are other ways to do it of course, but thats the simplest.

Related

How to make a datagrid see edits in a control?

I have a user control that I am using to populate a datagrid.
I would like the user to be able to add items by editing the empty row at the bottom. (This is why I am using a datagrid rather than an itemscontrol) However the datagrid does not realise that the last item is edited unless the user clicks the background of the control. I would like the new item to be added when the user makes changes on the properties that the control exposes.
XAML of the control:
<UserControl x:Class="ControlTest.MyControl"
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:ControlTest"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="300"
DataContext="{Binding Path=., Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
>
<StackPanel Orientation="Vertical">
<TextBox Text="{Binding Path=p1, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Width="300"
Height="30"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding Path=DropDownValues,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=local:MyControl}}"
SelectedItem="{Binding Path=p2, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Height="30"/>
</StackPanel>
</UserControl>
cs:
public partial class MyControl : UserControl
{
private static readonly DependencyProperty DropDownValuesProperty =
DependencyProperty.Register(
"DropDownValues",
typeof(List<String>),
typeof(MyControl),
new FrameworkPropertyMetadata(new List<String>()
));
public List<String> DropDownValues
{
get
{
return (List<String>)GetValue(DropDownValuesProperty);
}
set
{
SetValue(DropDownValuesProperty, value);
}
}
public MyControl()
{
InitializeComponent();
}
}
DataGrid XAML
<DataGrid
AutoGenerateColumns="False"
ItemsSource="{Binding objs, Mode=TwoWay}"
HeadersVisibility="None"
Margin="0,0,0.4,0"
CanUserAddRows="True"
>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
<DataGrid.Columns>
<DataGridTemplateColumn Width="300">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="local:Measure">
<local:MyControl
DataContext="{Binding ., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DropDownValues=
"{Binding DataContext.list, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
Width="300"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Can I make this work, and/or is there a better way to do this?
I would like to suggest you a different way of doing that:
set CanUserAddRows=false on your DataGrid and then manually add rows to the ObservableCollection<Something> to which your DataGrid is bound to.
OR
If you are still interested in the approach that you follow:
In your xaml file:
<DataGrid x:Name="myDataGrid" CellEditEnding="DataGrid_CellEditEnding" .....>
<!--Some Code-->
</DataGrid>
Then in the Code-Behind:
private void DataGrid_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
myDataGrid.CommitEdit();
}
If you don't understand anything then feel free to ask.
Update
If you are following the same approach:
In your DataGrid's Beginning edit event you can try:
private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e)
{
if ((selectedRow as DataGridRow).Item.ToString() != "{NewItemPlaceholder}")
{
//Here you can add the code to add new item. I don't know how but you should figure out a way
}
}
Note: The code mentioned above is not tested.
I would also suggest you :
Not to use DataGrid. Instead use ListBox. Because, you are trying to add some data. At this time you never need sorting, searching and column-reordering fascilities. In such scenario, ListBox is useful as it is light-weight control than datagrid. I have a sample here: https://drive.google.com/open?id=0B5WyqSALui0bTXFGZWxQUWVRdkU
Is the problem that the UI is not being notified of changes to the objs collection? What I would do is try setting up whatever view model that contains objs to make objs an observable collection. I would also ensure that whatever objs is an observable collection of implements INotifyPropertyChanged and that properties p1 and p2 both fire OnPorpertyChanged when they are set.
public ObservableCollection<YourObject> objs
and
public class YourObject : INotifyPropertyChanged
{
protected void OnPropertyChanged(string Name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(Name));
}
}
private string _p1;
public string p1
{
get { return _p1; }
set
{
if (_p1 != value)
{
_p1 = value;
OnPropertyChanged("p1");
}
}
}
private string _p2;
public string p2
{
get { return _p2; }
set
{
if (_p2 != value)
{
_p2 = value;
OnPropertyChanged("p2");
}
}
}
}

How to Dynamically change Datagrid Columns in xaml

Using the WPF DataGrid, I would like be able to change the columns displayed in xaml based on a property on the ViewModel.
The Idea is simply to change the set of Columns based on a property on the ViewModel. The various Views have columns in different combinations and all in different orders.
This should be trivial I thought but I can not find examples of where this has been done before
Any help would be appreciated.
Thanks.
At it's simplest:
Xaml
<Window
x:Class="Sample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="700">
<Window.Resources>
</Window.Resources>
<Grid>
<DataGrid
x:Name="grid"
ItemsSource="{Binding Persons}"
AutoGenerateColumns="False">
<!-- If Mode = City then
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="City" Binding="{Binding FavouriteCity}"/>
</DataGrid.Columns>
-->
<!-- If Mode = Colour then -->
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Colour" Binding="{Binding FavouriteColour}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code
namespace Sample {
public partial class MainWindow: INotifyPropertyChanged
{
public ObservableCollection<Person> Persons { get; set; }
public string Mode { get; set; }
public MainWindow() {
InitializeComponent();
Persons = new ObservableCollection<Person>()
{ new Person("John","Yellow","Paris"),
new Person("Anne","Green","Lagos"),
new Person("James","Pink","Brussels")
};
Mode = "City";
OnPropertyChanged("Persons");
OnPropertyChanged("Mode");
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Person
{
public string Name { get; set; }
public string FavouriteColour { get; set; }
public string FavouriteCity { get; set; }
public Person(string name, string favouriteColour, string favouriteCity)
{
Name = name;
FavouriteColour = favouriteColour;
FavouriteCity = favouriteCity;
} } }
There are many approaches to this I am sure, but the first thing I thought of was the VisualStateManager. See the MSDN here. You might start by reading the remarks at the bottom of that page - excerpted:
The VisualStateManager enables you to specify states for a control, the appearance of a control when it is in a certain state, and when a control changes states.
A caveat here is that I have not actually used the VSM yet; I merely came across it while answering another person's question. You may find his question to be an instructive example: Changing GridView Item height using VSM Triggers
The description of this class' purpose matches your use case though, and your implementation seems like a relatively straightforward extension of VSM examples.

Add attached event to DataGridColumn

I am trying to use the DataGridCheckBoxColumn in a grid and I noticed it has no events for checked or unchecked, for some reason.
I was trying to add attached events to this by creating a custom CBColumn class that inherits DataGridCheckBoxColumn.
The problem I am running into is that I am not sure how to add the handler to the exposed property since DataGridCheckBoxColumn is not derived from UIElement.
Therefore AddHandler and RemoveHandler are not available in this code block:
public event RoutedEventHandler Checked
{
add { AddHandler(CheckedEvent, value); }
remove { RemoveHandler(CheckedEvent, value); }
}
Any ideas on how to do this? I have looked all over with no luck.
EDIT: I am using MVVM, so I need to avoid Code Behind if possible.
Click event for DataGridCheckBoxColumn
<DataGridCheckBoxColumn Binding="{Binding Path=LikeCar}" Header="LikeCar">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Here is another solution in code. This is really rough, but it demonstrates the checked box and show a number values of what is checked in a text box.
<Window x:Class="DataGridCheckBoxItemTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DataGridCheckBoxItemTest"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<vm:DataGridTestVM />
</Window.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding Source}"
SelectedValue="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="10">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Test Checked" Binding="{Binding S}"/>
</DataGrid.Columns>
</DataGrid>
<TextBox HorizontalAlignment="Left"
Text="{Binding Test, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Height="39"
Margin="20,244,0,0"
TextWrapping="Wrap"
VerticalAlignment="Top"
Width="237"/>
</Grid>
namespace DataGridCheckBoxItemTest
{
public class DataGridTestVM : INotifyPropertyChanged
{
ObservableCollection<Source> source;
Source s;
int test;
public DataGridTestVM()
{
source = new ObservableCollection<Source>();
for (int i = 0; i < 10; i++)
{
s = new Source();
s.test = i;
source.Add(s);
}
}
public ObservableCollection<Source> Source
{
get
{
return source;
}
set
{
source = value;
OnPropertyChanged("Source");
}
}
public int Test
{
get
{
return test;
}
set
{
test = value;
OnPropertyChanged("Test");
}
}
public Source Selected
{
get
{
return s;
}
set
{
s = value;
Test = s.test;
OnPropertyChanged("Selected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
public class Source
{
public int test;
}
}
I ended up just reverting to a DataGridTemplateColumn and using the checkbox control in there. Didn't seem like there was a way to do what I wanted.

WPF two way binding with explicit source updating is not working

I have binding ObservableCollection - DataGrid (mode - TwoWay), but i want update collection by myself with UpdateSource() call and disable automathic source updating. I set binding like
ItemsSource="{Binding Path=Bezier.BezierPoints, Mode=TwoWay, UpdateSourceTrigger=Explicit}"
but my collection still updates automathically. My code samples are below. What am i doing wrong?
My XAML:
<DataGrid Name="BezierPointsDataGrid" Margin="5" AutoGenerateColumns="False"
Grid.Column="0" Grid.Row="0" Background="White"
ItemsSource="{Binding Path=Bezier.BezierPoints, Mode=TwoWay, UpdateSourceTrigger=Explicit}">
<DataGrid.Columns>
<DataGridTextColumn Header="X" Binding="{Binding Path=X}" Width="1*"/>
<DataGridTextColumn Header="Y" Binding="{Binding Path=Y}" Width="1*"/>
</DataGrid.Columns>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=UpdateBezierPointsCommand}" CommandParameter="{Binding ElementName=BezierPointsDataGrid}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
My ViewModel:
class BezierCurveViewModel : INotifyPropertyChanged
{
#region Bezier curve model
private BezierCurveModel _bezier;
public BezierCurveModel Bezier
{
get { return _bezier; }
set
{
if (_bezier == value)
return;
_bezier = value;
OnPropertyChanged("Bezier");
}
}
#endregion
#region Commands
public ICommand UpdateBezierPointsCommand { set; get; }
#endregion
#region Constructor
public BezierCurveViewModel()
{
UpdateBezierPointsCommand = new Command(a => ((DataGrid)a).GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateSource());
Bezier = new BezierCurveModel();
}
#endregion
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
My model:
public ObservableCollection<DPoint> BezierPoints { private set; get; }
EDIT: I changed ObservableCollection To DataTable to achieve expected behaviour. But i am still interested in solving this problem because i want to understand why any binding to observable collection updates source after editing table (read my comment to Andrew's post).
Here, you have set up the view to update the BezierPoints property explicitly, since that is what you are binding the ItemsSource.
I will assume that what you actually want is to use an Explicit update trigger on the properties of the individual points. To do this, you need to change the DataGridTextColum binding to UpdateSourceTrigger=Explicit.
As a side note, it would seem impossible that you could ever update the BezierPoints collection from the View at all because the property has a private setter.

2 viewmodels working on the same ObservableCollection, 1 viewmodel not updating

I have 2 viewmodels that inherit from the same BaseViewModel, which has an ObservableCollection as a public property.
The first viewmodel shows the ObservableCollection, while the second viewmodel allows for updating the collection.
How come the first view doesn't see the updated collection?
public class BaseViewModel : INotifyPropertyChanged
{
private Playlist _currentPlaylist;
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public Playlist CurrentPlaylist
{
get
{
if (_currentPlaylist == null)
{
_currentPlaylist = _library.CurrentPlaylist;
}
return _currentPlaylist;
}
set
{
_currentPlaylist = value;
NotifyPropertyChanged("CurrentPlaylist");
}
}
public BaseViewModel()
{
_library = new Library();
_dbContext = new MusicTrackerDataContext();
}
}
The first view that uses the inherited BaseViewModel uses CurrentPlaylist databound.
The second view sets the CurrentPlaylist again:
public class ArtistPageViewModel : BaseViewModel
{
public void PlaylistBtn_Clicked(ListView albumListView)
{
Library.AddSelectionToCurrentPlaylist(albumListView.SelectedItems.Cast<Album>());
CurrentPlaylist = Library.CurrentPlaylist;
}
}
Seeing that my BaseViewModel raises the INotifyPropertyChanged event when I set my CurrentPlaylist, I'd expect that the listview to which my CurrentPlaylist is bound, is updated.
What am I doing wrong?
Edit
The code for the View that's showing the old collection
public sealed partial class HubPage : Page
{
private HubPageViewModel _hubPageViewModel;
public HubPageViewModel HubPageViewModel
{
get
{
return _hubPageViewModel;
}
}
}
The XAML code for my HubPageViewModel
<Page
x:Class="MyProject.HubPage"
DataContext="{Binding HubPageViewModel, RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<HubSection x:Uid="HubSection5" Header="Now Playing"
DataContext="{Binding CurrentPlaylist}" HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<ListView
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding Tracks}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Title}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
</HubSection>
Edit 2
I've changed my code to the following (according to the comments)
public sealed partial class HubPage : Page
{
private readonly NavigationHelper navigationHelper;
private static HubPageViewModel _hubPageViewModel; // Made it static
public HubPageViewModel HubPageViewModel
{
get
{
return _hubPageViewModel;
}
}
}
<Page
x:Class="MyProject.HubPage"
DataContext="{Binding HubPageViewModel, RelativeSource={RelativeSource Self}}"
xmlns:vm="using:MyProject.ViewModels" // Added reference to my VM
mc:Ignorable="d">
<ResourceDictionary>
<vm:HubPageViewModel x:Name="hubPageViewModel"/> // Added the key
</ResourceDictionary>
<HubSection x:Uid="HubSection5" Header="Now Playing"
DataContext="{Binding Source={StaticResource hubPageViewModel}}" HeaderTemplate="{ThemeResource HubSectionHeaderTemplate}">
<DataTemplate>
<ListView
SelectionMode="None"
IsItemClickEnabled="True"
ItemsSource="{Binding CurrentPlaylist.Tracks}"
ItemClick="ItemView_ItemClick">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,27.5" Holding="StackPanel_Holding">
<TextBlock Text="{Binding Title}" Style="{ThemeResource ListViewItemTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</HubSection>
But it's still not updated when I return from my second viewmodel.
Even though both view models inherit the same base class, the state of the base class is not shared between the two view models. That is, HubPageViewModel and ArtistPageViewModel do not share the same instance of Playlist. They are completely different properties.
Since both view models points to the same playlist instance and that instance is an ObservableCollection, all changes to that instance of ObservableCollection will be shared between the two view models because both views are bound to the "collection changed" notifications for the instance they are watching. In your example, ArtistPageViewModel.PlaylistBtn_Clicked does not changes the data within the ObservableCollection, it changes the collection itself. This changes the collection that the second view is watching but does not change the one of the first view.

Categories

Resources