Guidance in creating a dependency property in WPF/MVVMLight application - c#

I have a WPF/MVVMLight application. My main view/viewmodel has a data grid with code behind in the viewmodel. I recently decided I'd like
this datagrid in a 2nd view, one view is live data, one view is showing history of same data. I don't want to duplicate this code in two places so I decided to create a usercontrol with a viewmodel and move the appropriate datagrid/code to the usercontrol.
Here is the issue/question: I would like to set the usercontrol->datagrid->itemsource to a property in my main viewmodel. I've seen some examples where people create dependencyproperties but I'm not sure how to handle this because I can't inherit DependencyObject in my viewmodel because it already inherits ViewModelBase (from mvvm light), so I can't use GetValue/SetValue as seen below. I'm very new to this so I may be missing something very obvious. While looking for solutions to my problem I'm seeing that MVVM Light has some messaging functionality, would this be a better way to approach this? Is there a better approach to this than what i'm taking? Thanks for any guidance.
Example not pulled from my code, just used to show the GetValue/SetValue that I can't figure out how to use because I can't inherit DependencyObject.
public class MyViewModel : ViewModelBase
{
public static DependencyProperty MyProperty =
DependencyProperty.Register("GridItemSource", typeof(EventData), typeof(MyViewModel),
new FrameworkPropertyMetadata() { BindsTwoWayByDefault = true });
public EventData GridItemSource
{
get { return (EventData)GetValue(MyProperty); }
set { SetValue(MyProperty, value); }
}
--- Attemping to be more clear in what i'm hoping to achieve..
Below is an example of the datagrid in my usercontrol. My user control has a VM with code behind with properties for the bindings you see below and some additional code related to the context menu.
The issue i'm trying to resolve is how to set the usercontrol datagrid itemsource now that it is in a usercontrol that is nested in my main view, and eventually will be nested in one more view. My mainview has a ObservableCollection with the data I want the usercontrol/datagrid/itemsource set to. My thinking was I could create a dependency property in the usercontrol that my main view could use to set the source.
So in my main view where I add the user control i want to do something like this:
<Views:MyUserControl SomePropertyInUserControl="{Binding MyObservableCollection, Mode=TwoWay}"/>
In my user control view:
<DataGrid Grid.Row="0" SelectedItem="{Binding DataGridSelectedItem, Mode=TwoWay}" HeadersVisibility="All" HorizontalAlignment="Stretch" Name="lbMain" ItemsSource="{Binding MonitorEventItems, Mode=TwoWay}" AutoGenerateColumns="False" SelectionMode="Single" >
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextMenuEventList,Mode=Default}"/>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTextColumn Header="Date" Binding="{Binding EventDateTime}" Width="Auto"/>
<DataGridTextColumn Header="Event Type" Binding="{Binding EventType}" Width="Auto"/>
<DataGridTextColumn Header="Folder/File" Binding="{Binding FFType}" Width="Auto"/>
<DataGridTextColumn Header="Name" Binding="{Binding EventFileFolderName}" Width="Auto"/>
<DataGridTextColumn Header="Full Path" Binding="{Binding EventFileFolderFullPath}" Width="*"/>
</DataGrid.Columns>
</DataGrid>

Do not use dependency properties in view models. Their main difference from ordinary properties is to be settable by bindings, styles, animations, etc., which you do not need in a view model.
Instead, create a property that raises the PropertyChanged event of the INotifyPropertyChanged interface, which is implemented by the ViewModelBase class. In addition, use ObservableCollection as property type, to notify about changes in the Items collection.
public class MyViewModel : ViewModelBase
{
private ObservableCollection<DataItem> items;
public ObservableCollection<DataItem> Items
{
get { return items; }
set
{
items = value;
RaisePropertyChanged("Items");
}
}
}

Same issue resolved here. (Note: does not use dependency properties) Example handling this with EventHandler and using MVVM Light Messaging. Other possible solutions/answers in thread as well.

Related

Access Datagridviewcolumn that is not in bound List

I want to add a DatagridCheckBoxColumn that is not in the bound source, so I can select specific rows via gui. Then I want to iterate over the datagrid and call a stored procedure with each selected row.
<DataGrid
x:Name="G_DG_Data"
Grid.Row="0"
Grid.RowSpan="1"
Grid.Column="0"
Grid.ColumnSpan="3"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
ClipboardCopyMode="IncludeHeader"
ItemsSource="{Binding MyGridData}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Mark" />
<DataGridTextColumn Binding="{Binding Data}" Header="Data" />
</DataGrid.Columns>
</DataGrid>
I tried to iterate over Datagrid.Items and Datagrid.Itemssource but I am unable to access the CheckBoxColumn values / the CheckBoxColumn.
Is this approach possible? Is there a better way?
EDIT:
I have this Property not in the ViewModel, because the ViewModel is EntityFramework with Database first and I fill the Datagrid directly from the Database, where the Property is useless.
private ObservableCollection<SDH_CRModul_Sniffer> myGridData;
public ObservableCollection<SDH_CRModul_Sniffer> MyGridData
{
get { return myGridData; }
set
{
if (myGridData == value) return;
myGridData = value;
OnPropertyChanged("MyGridData");
}
}
Where SDH_CRModul_Sniffer is the Entity created from Entity Framework.
The items in Items have no idea about any CheckBox in the UI. That's why you should bind it to a property of an item:
<DataGridCheckBoxColumn Binding="{Binding YourProperty}" Header="Mark" />
If you don't, the state of a CheckBox may get lost or corrupted as you scroll through the rows because of the UI virtualization that is enabled by default.
The only way to get the value of the CheckBox if you don't bind is to iterate through the visual elements in the UI, and this won't work if some of them are virtualized away.
I have this Property not in the ViewModel, because the ViewModel is EntityFramework with Database first and I fill the Datagrid directly from the Database, where the Property is useless.
Entity Framework generates partial classes so you could then create another partial class with the same name and add the property to this one. Or wrap the entity in a view model class and use this one in the DataGrid instead of the entity class.

ICollection.Add(item) donĀ“t raise propertychanged MVVM datagrid

My problem is this:
I use EF code first and I got a master detail situation, the master have a childs wich are the itemsSource for a datagrid, like this:
<DataGrid ItemsSource="{Binding Path=Transaction.StockItems,Mode=TwoWay}"
SelectedItem="{Binding Path=StockItem, Mode=TwoWay}"
AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<mui:DataGridTextColumn Width="0.12*" Binding="{Binding Name}"/>
<mui:DataGridTextColumn Width="0.12*" Binding="{Binding Cod}"/>
<mui:DataGridTextColumn Width="0.12*" Binding="{Binding Cost}"/>
<mui:DataGridTextColumn Width="0.15*" Binding="{Binding Qt}"/>
</DataGrid.Columns>
</DataGrid>
In my ViewModel I got a property for the Transaction property, like this:
private tTran_t;
public tTransaccion Transaction
{
get { return _t; }
set { _t= value; RaisePropertyChanged("Transaction"); }
}
In other method of my ViewModel I do an Add() to the collection, like this:
Transaction.StockItems.Add(myNewLine);
RaisePropertyChanged("Transaction");
In my Model the definition of Transaction.StockItems is ICollection.
The problem: the datagrid never got updated
I guess this is because the RaisPropertyChanged() is never called, because the collection is never set, but as you can see, I do call the RaisPropertyChanged() of the Transaction object.
Thanks in advance
adding an item to a collection doesn't "change" the object itself. a RaisepropertyChanged is raised when the collection itself is set. so what you need to do is assigning a new collection to your collection reference. So First you add item to a temporary collection then you do transaction = new collection(temporaryCollection) then your UI will ne notified of a change.

Binding to a property of a collection

Context
The WPF datagrid allows developers to manually bind each column to a property of the ItemsSource, like this:
<DataGrid ItemsSource="{Binding People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"/>
<DataGridTextColumn Binding="{Binding Age}"/>
<DataGridTextColumn Binding="{Binding Country}"/>
</DataGrid.Columns>
</DataGrid>
The advantage of these column bindings is that Visual Studio will display design-time warnings if the bindings don't correspond to a property of the objects in the People collection. It will also provide a list of valid properties through intellisense.
I'm developing a different type of grid and am having trouble getting this functionality. Currently I have developed something like this:
<my:Grid ItemsSource="{Binding People}">
<my:Column Property="Name" />
<my:Column Property="Age" />
<my:Column Property="Country" />
</my:Grid>
Internally these string based properties are looked up through type.GetProperty() at runtime.
My Question
How can I implement property binding like in the DataGrid example? I have looked through the decompiled source of DataGrid and found that DataGridBoundColumn.Binding is of type BindingBase.
I reappropriated BindingBase into my custom grid but I'm still not getting strong typing or intellisense options, probably because BindingBase has no idea what type it applies to, but I don't see how to provide it with the context it needs.
Change your implemantion of the property my:Column.Property to a Dependency Propertie. That will allow you to use Bindings, maybe that helps. But the intellisense around Binding is more a deal of the VS-XAML-designer and not your code.
//http://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx
public static readonly DependencyProperty PropertyProperty =
DependencyProperty.Register(
"Property", typeof(Boolean),
);
public bool IsSpinning
{
get { return (bool)GetValue(PropertyProperty ); }
set { SetValue(PropertyProperty , value); }
}

How to keep the current sort of a ICollection after data refresh?

I've a problem in my application Client/Server. I work with the MVVM pattern. In my view, I've a DataGrid which is bound with a ICollection in my ViewModel with these lines of code :
public ICollectionView Customers
{
get
{
return _customers;
}
set
{
_customers= value;
RaisePropertyChanged("Customers");
}
}
_customers = CollectionViewSource.GetDefaultView(Manager.Instance.Customers());
_customers .SortDescriptions.Add(new SortDescription("CreationDate", ListSortDirection.Descending));
At the start, these two lines work fine : my DataGrid has my list of customers and the sort is ok.
But when the server update my list of customer, i would like to Update my collection and, so, my DataGrid.
So, I received a notification with my new List of customers. When I received this notification, I update my collection with these lines :
Customers = CollectionViewSource.GetDefaultView(e.CustomersInfo);
_customers.SortDescriptions.Clear();
_customers .SortDescriptions.Add(new SortDescription("CreationDate", ListSortDirection.Descending));
Customers.Refresh();
Here, my DataGrid is well refresh with the good data, but the sort is not refresh beacause, at the beginning, my list of customers is sorted by the CreationDate, but after the refresh, my list is sorted by CustomersName.
Here, my XAML code :
<DataGrid AutoGenerateColumns="false" ItemsSource="{Binding Customers, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Name="dtgEventInfo" SelectedItem="{Binding SelectedCustomers, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ItemContainerStyle="{StaticResource ItemContStyle}" IsEnabled="True" IsReadOnly="True" Margin="0,0,330,0" GridLinesVisibility="None" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, Mode=TwoWay}" Header="Name" MinWidth="40" Width="Auto"/>
<DataGridTextColumn Binding="{Binding CreationDate, Mode=TwoWay}" Header="Date" MinWidth="50" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
Do you have any ideas to help me please ? After some researches, I've bot found any solutions...
I think you should just replace RaisePropertyChanged("Events"); by RaisePropertyChanged("Customers");
You can try 2 things
Implement INotifyPropertyChanged Interface in your ViewModel class which is cappable of updating UI when required. Please see the http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx
Use ObservableCollection instead of CollectionView, which is already implemented INotifyPropertyChanged interface and updates the UI when required. Sorting operation also possible by implementing IComparer Interface. Please see the link http://msdn.microsoft.com/en-us/library/ms668604.aspx
Please mark the answer if useful
when need the behavior you want, i do the follwoing:
i create an ObservableCollection of my type ONCE eg. _mysource
i create a ICollectionView ONCE eg. _myview
i use Clear/Add/Remove to update _mysource
sorting/grouping/filtering are applied, on _myview.Refresh()
so this would work for you
this._mysource.Clear();
this._mysource.AddRange(e.CustomersInfo);
_myview.Refresh();
I guess your updating method is running in different thread from main thread. You can try this:
if (Application.Current.Dispatcher.CheckAccess())
{
UpdateCollectionView(e.CustomersInfo);
}
else
{
Application.Current.Dispatcher.Invoke(new Action(() => UpdateCollectionView(e.CustomersInfo)));
}
private void UpdateCollectionView(IEnumerable<Customer> customers)
{
Customers = CollectionViewSource.GetDefaultView(customers);
Customers.SortDescriptions.Clear();
Customers.SortDescriptions.Add(new SortDescription("CreationDate", ListSortDirection.Descending));
Customers.Refresh();
}
After some searches and lots of debug mode in Visual Studio, I think I've found my problem. In my manager Manager.cs, when I received my notification, I did this:
ObservableCollection<CustomerInfo> collection = new ObservableCollection<CustomerInfo>(e.CustomersInfoList);
CustomerListViewModel.Instance.Customers = collection;
So, this new instantiation can probably cause my problem, because, on this same collection, I realize some filter, and, the sort is OK after the filter method!
So do you have any ideas now?

How do I set the ItemsSource of a DataGrid in XAML?

I'm trying to set the ItemsSource property of a DataGrid named dgIssueSummary to be an ObservableCollection named IssueSummaryList. Currently, everything is working when I set the ItemsSource property in my code-behind:
public partial class MainPage : UserControl
{
private ObservableCollection<IssueSummary> IssueSummaryList = new ObservableCollection<IssueSummary>
public MainPage()
{
InitializeComponent();
dgIssueSummary.ItemsSource = IssueSummaryList
}
}
However, I'd rather set the ItemsSource property in XAML, but I can't get it to work. Here's the XAML code I have:
<sdk:DataGrid x:Name="dgIssueSummary" AutoGenerateColumns="False"
ItemsSource="{Binding IssueSummaryList}" >
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding ProblemType}" Header="Problem Type"/>
<sdk:DataGridTextColumn Binding="{Binding Count}" Header="Count"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
What do I need to do to set the ItemsSource property to be the IssueSummaryList in XAML rather than C#?
You need to make "IssueSummaryList" a property. If you do this, you can bind to it directly. You can't bind via Xaml to a private field.
You'll also need to set the DataContext to "this" (or use another method to get it to find the appropriate instance).
Your IssueSummaryList is private. You need to make it a Property with get and set
public ObservableCollection<IssueSummary> IssueSummaryList
{
get
{
// ...
}
}
The XAML is correct, so the problem must be in the Binding.
Is the ObservableCollection exposed as a property?
How have you set the Binding? In the most simple case you use code like:
this.DataContext=this;
in the Window_Load eventhandler

Categories