I'm making a Window to manage the users who using laptop. I have the window named "LaptopWindow" which contain a TextBox to display the user id of the one using it. I have made a button to open new UserControl named "FindEmployeeUC" to find the "EmpID" by select the row in DataGrid of UserControl and pass it back to the TextBox in "LaptopWindow".
I got the selected row of the DataGrid and use the property name "SelectedUA" to hold it inside the view model "UserAccountViewModel".
When OnPropertyChanged event fire I call the instance of "LaptopManagementViewModel" (this view model is bound with "LaptopWindow") and set the EmpID to the TextBox in "LaptopWindow" by the property named "ReceiverID"
The property "ReceiverID" got value but the UI of "LaptopWindow" didn't get update.
I tried to use Delegate, Singleton pattern, It had the same result.
Here is some code to explain more what I'm facing
The "LaptopWindow" xaml:
<StackPanel Grid.Row="2" Grid.Column="1" Style="{StaticResource inputControl}">
<TextBlock Text="Người nhận"/>
<TextBox Name="txtReceiver" Text="{Binding ReceiverID,Source={StaticResource vmLaptopManagement}}" Margin="0,0,30,0"/>
</StackPanel>
<!--Button open FindEmpUC -->
<Button Grid.Row="2" Grid.Column="1" Width="30" Height="29" HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Transparent" Margin="0,4,4,4" Command="{Binding CmdFindEmp}">
<Image Source="/imgs/find-48.png" Stretch="Uniform" />
</Button>
The "LaptopManagementViewModel":
//the userAccountVM
UserAccountViewModel userAccountVM;
//the constructor
public LaptopManagementViewModel(UserAccountViewModel userAccountVM)
{
LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();
LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();
this.userAccountVM = userAccountVM;
ReceiverID = this.userAccountVM.SelectedUA.EmpID;
}
//the ReceiverID property
string receiverID;
public string ReceiverID
{
get { return receiverID; }
set
{
receiverID = value;
OnPropertyChanged("ReceiverID");
}
}
//function open FindEmployeeUC
private void FindEmployee(object obj)
{
//show findEmployee UC
Window wd = new Window()
{
Content = new FindEmployeeUC(),
};
wd.ShowDialog();
}
The "FindEmployeeUC" xaml:
<DataGrid Grid.Row="1" ItemsSource="{Binding LstUA}" CanUserAddRows="False" SelectedItem="{Binding SelectedUA,Mode=TwoWay}" AutoGenerateColumns="False" ColumnWidth="*" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"></DataGridTextColumn>
<DataGridTextColumn Header="EmpID" Binding="{Binding EmpID}"></DataGridTextColumn>
<DataGridTextColumn Header="EmpName" Binding="{Binding EmpName}"></DataGridTextColumn>
<DataGridTextColumn Header="Position" Binding="{Binding Position}"></DataGridTextColumn>
<DataGridTextColumn Header="LineGroup" Binding="{Binding LineGroup}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The "UserAccountViewModel":
//The property "SelectedUA"
UserAccountModel selectedUA;
public UserAccountModel SelectedUA
{
get { return selectedUA; }
set
{
if(selectedUA!=value)
{
selectedUA = value;
LaptopManagementViewModel laptopVM = new LaptopManagementViewModel(this);
OnPropertyChanged("SelectedUA");
}
}
}
I expect to get the EmpID for the TextBox in "LaptopWindow". I attach a picture for more detail:
Thanks in advance!
In your OnPropertyChanged event invocator you are always creating a new instance of UserAccountViewModel. This instance is never referenced in your XAML code, therefore your view can't see this new instances.
Since view model have a state you typically use a single instance for a binding target.
I removed the parameterized constructor to enable the instantiation in XAML (the instance is assigned to the UserAccountVM property from XAML) and also removed the reference to LaptopManagementViewModel from UserAccountViewModel. I created the view model instances and added them to the ResourceDictionary of App.xaml.
I also added a PropertyChanged event handler to the LaptopManagementViewModel to listen for changes of UserAccountViewModel.SelectedUA.
It is also highly recommended to avoid string literals. Instead of calling OnPropertyChanged("MyProperty") you should use the free compiler support by applying nameof(): OnPropertyChanged(nameof(MyClass.MyProperty)). I replaced the corresponding code. You now get rid of typos and get full support of compiler checks and refactoring tools (e.g. renaming).
Also stay away from Singletons. They smell strong.
Last complaint: make fields always private (or protected), especially when they are property backing fields. If you don't use any access modifier then internal will apply implicitly. Which is equivalent to public inside a shared assembly and fields should never be exposed.
Microsoft Docs recommends :
Generally, you should use fields only for variables that have private or protected accessibility. Data that your class exposes to client code should be provided through methods, properties and indexers. By using these constructs for indirect access to internal fields, you can guard against invalid input values. A private field that stores the data exposed by a public property is called a backing store or backing field.
App.xaml
<Application x:class="App">
<Application.Resources>
<ResourceDictionary>
<UserAccountViewModel x:Key="UserAccountViewModel" />
<LaptopManagementViewModel x:Key="LaptopManagementViewModel">
<LaptopManagementViewModel.UserAccountVM>
<StaticResource ResourceKey="UserAccountViewModel" />
</LaptopManagementViewModel.UserAccountVM>
</LaptopManagementViewModel>
</Application.Resources>
</Application>
LaptopWindow.xaml
<Window x:class="LaptopWindow">
<Window.DataContext>
<StaticResource ResourceKey="LaptopManagementViewModel" />
</Window.DataContext>
...
</Window>
FindEmployeeUC.xaml
<Window x:class="FindEmployeeUC">
<Window.DataContext>
<StaticResource ResourceKey="UserAccountViewModel" />
</Window.DataContext>
<DataGrid
...
</DataGrid>
</Window>
LaptopManagementViewModel.cs
public class LaptopManagementViewModel
{
private UserAccountViewModel userAccountVM;
public UserAccountViewModel UserAccountVM
{
get => userAccountVM;
set
{
userAccountVM = value;
OnPropertyChanged(nameof(UserAccountVM));
if (userAccountVM != null)
{
// Always clean up event handlers to avoid memory leaks
userAccountVM.PropertyChanged -= UpdateReceiverIdOnPropertyChanged;
}
userAccountVM.PropertyChanged += UpdateReceiverIdOnPropertyChanged;
}
}
// The constructor is now parameterless for the use in XAML
public LaptopManagementViewModel()
{
LstDVUS = LaptopManagementBLL.Instance.GetDVUsageStatuses();
LstLaptop = LaptopManagementBLL.Instance.GetLaptopsInfo();
}
// UserAccountVM.PropertyChanged event handler
private void UpdateReceiverIdOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName.Equals(nameof(UserAccountViewModel.SelectedUA), StringComparison.OrdinalIgnoreCase))
{
ReceiverID = UserAccountVM.SelectedUA.EmpID;
}
}
private string receiverID;
public string ReceiverID
{
get { return receiverID; }
set
{
receiverID = value;
OnPropertyChanged(nameof(ReceiverID));
}
}
}
UserAccountViewModel.cs
public class UserAccountViewModel
{
private UserAccountModel selectedUA;
public UserAccountModel SelectedUA
{
get => selectedUA;
set
{
if(selectedUA!=value)
{
// Removed wrong creation of LaptopManagementViewModel instances
selectedUA = value;
OnPropertyChanged(nameof(SelectedUA));
}
}
}
}
Related
C#:
public void SetCompetition(Window wT1)
{
//Add all the Copetition
wT1._competition = new List<Competition>();
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test1", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test2", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test3", IsSelected = false });
wT1._competition.Add(new Competition { Logo = "3.png", Name = "test4", IsSelected = false });
wT1.cboSetupCompetition.ItemsSource = wT1._competition;
wT1.cboSetupCompetition.Items.Refresh();
}
Data Template:
<UserControl.Resources>
<System:Double x:Key="Double1">11</System:Double>
<DataTemplate x:Key="cmbCompetition">
<WrapPanel Height="30" >
<Label Content="{Binding Name}" ></Label>
</WrapPanel>
</DataTemplate>
</UserControl.Resources>
<ComboBox x:Name="cboSetupCompetition" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" IsEditable="True" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
I have a Combobox with a label and an image and when I select an item I would like to see the same format in the Combobox when it is closed. I am not getting any errors I am seeing the name of the application.Competition(this is my object Model) instead of the values of the image and label.
The SetCopetition is invoked when the application loads.
A TextBox is not able to display a Label and an Image or whatever elements that are in your DataTemplate in it.
Set the IsEditable property of the ComboBox to false and it should work as expected, i.e. your DataTemplate will be applied to the selected item when the ComboBox is closed:
<ComboBox x:Name="cboSetupCompetition" IsEditable="False" ItemTemplate="{DynamicResource cmbCompetition}" HorizontalAlignment="Left" Margin="29,28,0,0" VerticalAlignment="Top" Width="173" RenderTransformOrigin="0.5,0.591" FontSize="12" Height="22" Background="#FFD8D8D8" SelectionChanged="UpdateCompetitionSelection"/>
Your issue has nothing to do with MVVM...
the specific problem as Mn8 spotted is that IsEditable=true forces the combo to display a textbox as the selected item
However you are still thinking winforms not WPF, using code behind to link data into the view causes many problems and instability as quite often this breaks the binding connections which is what is suspected was your problem initially, using a proper MVVM approach will eliminate all these problems
the best overveiw of MVVM i know of is
https://msdn.microsoft.com/en-gb/library/hh848246.aspx
Model
this is your data layer, it handle storage and access to data, your model will handle access to files, databases, services, etc
a simple model would be
public class Model
{
public string Text { get; set; }
public Uri Uri { get; set; }
}
ViewModel
on top of your Model you have your View Model
this manages the interaction of your View with the model
for example here because it uses Prism's BindableBase the SetProperty method notifies the View of any changes to the data, the ObservableCollection automatically notifies of changes to the collection, it also uses Prism's DelegateCommand to allow method binding in the view
public class ViewModel:BindableBase
{
public ViewModel()
{
AddItem = new DelegateCommand(() => Collection.Add(new Model()
{
Text = NewText,
Uri = new Uri(NewUri)
}));
}
private string _NewText;
public string NewText
{
get { return _NewText; }
set { SetProperty(ref _NewText, value); }
}
private string _NewUri;
public string NewUri
{
get { return _NewUri; }
set { SetProperty(ref _NewUri, value); }
}
private Model _SelectedItem;
public Model SelectedItem
{
get { return _SelectedItem; }
set
{
if (SetProperty(ref _SelectedItem, value))
{
NewText = value?.Text;
NewUri = value?.Uri.ToString();
}
}
}
public ObservableCollection<Model> Collection { get; } = new ObservableCollection<Model>();
public DelegateCommand AddItem { get; set; }
}
View
the View ideally does nothing but displays and collects data, all formatting / Styling should be done here
firstly you need to define the data source, the usual way is via the data context as this auto inherits down the visual tree, in the example because i set the window's datacontext, i have also set it for everything in the window the only exception is the dataTempplate as this is set to the current item in the collection
i then bind properties to the datasource
Note the code behind file is only the default constructor no other code at all
<Window
x:Class="WpfApplication1.MainWindow"
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:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<GroupBox Header="Text">
<TextBox Text="{Binding NewText}"/>
</GroupBox>
<GroupBox Header="URI">
<TextBox Text="{Binding NewUri}"/>
</GroupBox>
<Button Content="Add" Command="{Binding AddItem}"/>
<ComboBox ItemsSource="{Binding Collection}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Uri}" />
<TextBlock Text="{Binding Text}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</Window>
I'm trying to validate a form in a UserControl element that is being used by another UserControl that is inside a Window.
I'm using MVVM pattern and i'm implementing the INotifyDataErrorInfo in the ViewModel of the last UserControl child.
The problem is that, when an error occurs, both, the TextBox inside the UserControl that binds to the property that has generated the error, and the UserControl itself get surrounded by a red box indicating the error, and i want just the TextBox to be highlighted.
Here is the code:
The Window that has the MainView (or the first UserControl):
<Grid>
<pages:MainPage>
<pages:MainPage.DataContext>
<vm:MainViewModel/>
</pages:MainPage.DataContext>
</pages:MainPage>
</Grid>
(It just contains a UserControl as a page)
The UserControl of the "MainPage", that contains the other (and last) UserControl as a page inside a page:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary>
<DataTemplate DataType="{x:Type vm:SearchViewModel}">
<pages:SearchPage/>
</DataTemplate>
...
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
...
<ContentControl Content="{Binding CurrentPage}"/>
Ok, now beleive me, "CurrentPage" has a ViewModel object taken from a MainViewModel property, so lets suppose that "CurrentPage" is a "SearchViewModel" object, so there we have the SearchPage UserControl.
And now the last UserControl, the SearchPage:
<TextBox Grid.Column="1" Grid.Row="0" Text="{Binding CaseNumber}"/>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding PatientNumber}"/>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding PatientName}"/>
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding PatientFamilyName}"/>
<TextBox Grid.Column="1" Grid.Row="4" Text="{Binding PatientMotherMaidenName}"/>
<TextBox Grid.Column="1" Grid.Row="5" Text="{Binding DoctorName}"/>
Just to make the post as small as possible, i've just added the "form" section of the UserControl.
And now the most important part, the SearchViewModel with the INotifyDataErrorInfo implementation:
public class SearchViewModel : ViewModelBase, INotifyDataErrorInfo, IVMPage
{
private SearchModel searchModel = new SearchModel();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();
private string patientNumber;
public string PatientNumber
{
get { return patientNumber; }
set
{
int number;
patientNumber = value;
if (int.TryParse(value, out number))
{
searchModel.PatientNumber = number;
ClearErrors("PatientNumber");
}
else
{
AddErrors("PatientNumber", new List<string> { "The value must be a number" });
}
RaisePropertyChanged("PatientNumber");
}
}
private string caseNumber;
public string CaseNumber
{
get { return caseNumber; }
set
{
int number;
caseNumber = value;
if (int.TryParse(value, out number))
{
searchModel.CaseNumber = number;
ClearErrors("CaseNumber");
}
else
{
AddErrors("CaseNumber", new List<string> { "The value must be a number" });
}
RaisePropertyChanged("CaseNumber");
}
}
....
private void ClearErrors(string propertyName)
{
errors.Remove(propertyName);
if (ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
private void AddErrors(string propertyName, List<string> newErrors)
{
errors.Remove(propertyName);
errors.Add(propertyName, newErrors);
if(ErrorsChanged != null)
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if(string.IsNullOrEmpty(propertyName))
{
return errors.Values;
}
else
{
if(errors.ContainsKey(propertyName))
{
return errors[propertyName];
}
else
{
return null;
}
}
}
public bool HasErrors
{
get { return (errors.Count() > 0); }
}
So, the problem is:
For example, if i introduce characters in "CaseNumber" TextBox, it is surrounded with a red line indicating the error AND all the SearchPage UserControl is also surrounded with another red line. What i want is just to mark the TextBox with the red line to indicate the error and NOT all the UserControl.
The curious thing is that, if i comment the sections at AddError and ClearError methods where the ErrorChanged event is fired, the UserControl is no longer surrounded with the red line... But i don't lnow why...
Sorry for the long question and thanks.
Ok, the answer is simple.
The problem was with this line:
<ContentControl Content="{Binding CurrentPage}"/>
Because WPF by default sets the ValidatesOnNotifyDataErrors property to true, when an error happens inside the "CurrentPage" UserControl, the TextBox that generated the error inside the UserControl indicates the error with a red line arround him, as expected, BUT ALSO the ContentControl checks the "GetErrors" method and draws another redline arround all the "CurrentPage" UserControl.
To avoid this and just indicate the error at the TextBox and not all the UserControl, just had to add this to the ContentControl declaration:
<ContentControl Content="{Binding CurrentPage, ValidatesOnNotifyDataErrors=False}"/>
Brief version
Color the background of the text box instead of the border.
(optional) Detailed version
Been there, run into that problem. The border of a textbox is reasonably difficult to change, as so many things play with it. For example, if you are using DevExpress, you have to override the whole textbox style to get at the border, then you start to lose the natural highlighting when the box is selected, etc.
Thus, I suggest coloring the background of the textbox to indicate an error. Its much more obvious to the user, looks great, and works well in practice.
Use a very light red color, this page is good for finding colors that are in harmony with the existing color scheme of your page:
https://color.adobe.com/create/color-wheel/
WPF and MVVM are like body and soul. And it makes sense for ViewModel to be oblivious of the View that it may connect to (and vice versa).
But is it even a sin to hold reference of the View's Resource Dictionary inside a ViewModel. Does that defeat the purpose?
e.g. the code below is for POC purpose if VM can hold Views reference via resource dictionary. ViewModel can change this resource dictionary on the fly (based on certain input parameters).
MyViewModel.cs
public interface IViewInjectingViewModel
{
void Initialize();
URI ViewResourceDictionary { get; }
}
public class MyViewModel : IViewInjectingViewModel
{
private URI _viewResourceDictionary;
public void Initialize()
{
_viewResourceDictionary = new URI("pack://application:,,,/MyApplication;component/Resources/MyApplicationViews.xaml");
}
public URI ViewResourceDictionary
{
get
{
return _viewResourceDictionary;
}
}
}
MyApplicationViews.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate DataType="{x:Type local:MyViewModel}">
<StackPanel>
<TextBlock
Text="Portfolios" FontFamily="Verdana"
FontSize="16" FontWeight="Bold" Margin="6,7,6,4"/>
<ListBox
Margin="2,1" SelectionMode="Single"
ItemsSource="{Binding AvailableTraders}"
SelectedItem="{Binding SelectedTrader}" DisplayMemberPath="Name">
<!-- ... -->
</ListBox>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
MainWindow.xaml
<Window ...>
<ContentControl
DataContext="{Binding myViewModel}"
local:MyBehaviors.InjectView="true"/>
</Window>
CommonBehaviors:
public static class MyBehaviors
{
public static readonly DependencyProperty InjectViewProperty
= DependencyProperty.RegisterAttached(..);
//attached getters and setters...
private static void OnInjectViewPropertyChanged(..)
{
var host = o as ContentControl;
if ((bool)e.NewValue)
{
host.DataContextChanged
+= (o1, e1) =>
{
var viewInjectingVM = host.DataContext as IViewInjectingViewModel;
if (viewInjectingVM != null)
{
host.Resources.MergedDictionaries.Clear();
host.Resources.MergedDictionaries.Add(
new ResourceDictionary() {
Source = viewInjectingVM.ViewResourceDictionary
});
host.Content = viewInjectingVM;
}
};
}
}
}
Okay, I'll take a shot. I think this is okay. If you consider view-models to be part of the presentation layer, then having the view-model change a view's resource dictionary in response to some action is well within the MVVM paradigm.
Essentially you are changing the view's presentation in response to some action and the view-model has this responsibility. So, updating the resource dictionary in this context seems like valid in the MVVM pattern.
I am writing a WPF application where where i need to display custom file iformation which consists of field name & its value. I generate a grid rumtime with label & textboxes. I display the field name in label & field value in textbox(i want it to be editable). & each time file selection changes, number of field change & so the grid columns & rows. Right now I am generating this grid in code behind . Is there any way i can do it in XAml with view model.
This is pretty easy to do with an ItemsControl. If you ViewModel exposes a list of metadata objects, say a class like this:
public class FileMetaData : INotifyPropertyChanged
{
private string name;
private string value;
public event PropertyChangedEventHandler PropertyChanged = (o, e) => { };
public string Name
{
get { return name; }
set
{
name = value;
PropertyChanged(this, new PropertyChangedEventArgs("Name"));
}
}
public string Value
{
get { return value; }
set
{
this.value = value;
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
}
Then, your ViewModel would expose it as an ObservableCollection (so WPF knows when new items are added or removed):
public class MyViewModel
{
...
public ObservableCollection<FileMetaData> Files { get; private set; }
...
}
Then, your view would use an ItemsControl with an ItemTemplate to display it:
<ItemsControl ItemsSource="{Binding Files}" Grid.IsSharedSizeScope="True">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="one" />
<ColumnDefinition Width="Auto" SharedSizeGroup="two" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" />
<TextBox Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that I'm setting Grid.IsSharedSizeScope to true on the ItemsControl, so the columns will align. If you have a lot of data, you'll probably want to wrap this in a ScrollViewer (or better retemplate the ItemsControl to have one).
I'm not sure why you're creating this grid at runtime. You should look into using a standard presentation method such as a <ListBox> with a custom item template. Always look to use declaritive definition of your UI (within the XAML) instead of the codebehind.
I've got a blog post on creating a checked listbox that shows some of the details, but you should be able to find other good examples out there as well.
I am creating a WPF window with a DataGrid, and I want to show the blank "new item" row at the bottom of the grid that allows me to add a new item to the grid. For some reason, the blank row is not shown on the grid on my window. Here is the markup I used to create the DataGrid:
<toolkit:DataGrid x:Name="ProjectTasksDataGrid"
DockPanel.Dock="Top"
Style="{DynamicResource {x:Static res:SharedResources.FsBlueGridKey}}"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedProject.Tasks}"
RowHeaderWidth="0"
MouseMove="OnStartDrag"
DragEnter="OnCheckDropTarget"
DragOver="OnCheckDropTarget"
DragLeave="OnCheckDropTarget"
Drop="OnDrop"
InitializingNewItem="ProjectTasksDataGrid_InitializingNewItem">
<toolkit:DataGrid.Columns>
<toolkit:DataGridCheckBoxColumn HeaderTemplate="{DynamicResource {x:Static res:SharedResources.CheckmarkHeaderKey}}" Width="25" Binding="{Binding Completed}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Days" Width="75" Binding="{Binding NumDays}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Due Date" Width="75" Binding="{Binding DueDate, Converter={StaticResource standardDateConverter}}" IsReadOnly="false"/>
<toolkit:DataGridTextColumn Header="Description" Width="*" Binding="{Binding Description}" IsReadOnly="false"/>
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
I can't figure out why the blank row isn't showing. I have tried the obvious stuff (IsReadOnly="false", CanUserAddRows="True"), with no luck. Any idea why the blank row is disabled? Thanks for your help.
You must also have to have a default constructor on the type in the collection.
Finally got back to this one. I am not going to change the accepted answer (green checkmark), but here is the cause of the problem:
My View Model wraps domain classes to provide infrastructure needed by WPF. I wrote a CodeProject article on the wrap method I use, which includes a collection class that has two type parameters:
VmCollection<VM, DM>
where DM is a wrapped domain class, and DM is the WPF class that wraps it.
It truns out that, for some weird reason, having the second type parameter in the collection class causes the WPF DataGrid to become uneditable. The fix is to eliminate the second type parameter.
Can't say why this works, only that it does. Hope it helps somebody else down the road.
Vincent Sibal posted an article describing what is required for adding new rows to a DataGrid. There are quite a few possibilities, and most of this depends on the type of collection you're using for SelectedProject.Tasks.
I would recommend making sure that "Tasks" is not a read only collection, and that it supports one of the required interfaces (mentioned in the previous link) to allow new items to be added correctly with DataGrid.
In my opinion this is a bug in the DataGrid. Mike Blandford's link helped me to finally realize what the problem is: The DataGrid does not recognize the type of the rows until it has a real object bound. The edit row does not appear b/c the data grid doesn't know the column types. You would think that binding a strongly typed collection would work, but it does not.
To expand upon Mike Blandford's answer, you must first assign the empty collection and then add and remove a row. For example,
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// data binding
dataGridUsers.ItemsSource = GetMembershipUsers();
EntRefUserDataSet.EntRefUserDataTable dt = (EntRefUserDataSet.EntRefUserDataTable)dataGridUsers.ItemsSource;
// hack to force edit row to appear for empty collections
if (dt.Rows.Count == 0)
{
dt.AddEntRefUserRow("", "", false, false);
dt.Rows[0].Delete();
}
}
Add an empty item to your ItemsSource and then remove it. You may have to set CanUserAddRows back to true after doing this. I read this solution here: (Posts by Jarrey and Rick Roen)
I had this problem when I set the ItemsSource to a DataTable's DefaultView and the view was empty. The columns were defined though so it should have been able to get them. Heh.
This happned to me , i forgot to new up the instance and it was nightmare for me . once i created an instance of the collection in onviewloaded it was solved.
`observablecollection<T> _newvariable = new observablecollection<T>();`
this solved my problem. hope it may help others
For me the best way to implement editable asynchronous DataGrid looks like that:
View Model:
public class UserTextMainViewModel : ViewModelBase
{
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
this._isBusy = value;
OnPropertyChanged();
}
}
private bool _isSearchActive;
private bool _isLoading;
private string _searchInput;
public string SearchInput
{
get { return _searchInput; }
set
{
_searchInput = value;
OnPropertyChanged();
_isSearchActive = !string.IsNullOrEmpty(value);
ApplySearch();
}
}
private ListCollectionView _translationsView;
public ListCollectionView TranslationsView
{
get
{
if (_translationsView == null)
{
OnRefreshRequired();
}
return _translationsView;
}
set
{
_translationsView = value;
OnPropertyChanged();
}
}
private void ApplySearch()
{
var view = TranslationsView;
if (view == null) return;
if (!_isSearchActive)
{
view.Filter = null;
}
else if (view.Filter == null)
{
view.Filter = FilterUserText;
}
else
{
view.Refresh();
}
}
private bool FilterUserText(object o)
{
if (!_isSearchActive) return true;
var item = (UserTextViewModel)o;
return item.Key.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase) ||
item.Value.Contains(_searchInput, StringComparison.InvariantCultureIgnoreCase);
}
private ICommand _clearSearchCommand;
public ICommand ClearSearchCommand
{
get
{
return _clearSearchCommand ??
(_clearSearchCommand =
new DelegateCommand((param) =>
{
this.SearchInput = string.Empty;
}, (p) => !string.IsNullOrEmpty(this.SearchInput)));
}
}
private async void OnRefreshRequired()
{
if (_isLoading) return;
_isLoading = true;
IsBusy = true;
try
{
var result = await LoadDefinitions();
TranslationsView = new ListCollectionView(result);
}
catch (Exception ex)
{
//ex.HandleError();//TODO: Needs to create properly error handling
}
_isLoading = false;
IsBusy = false;
}
private async Task<IList> LoadDefinitions()
{
var translatioViewModels = await Task.Run(() => TranslationRepository.Instance.AllTranslationsCache
.Select(model => new UserTextViewModel(model)).ToList());
return translatioViewModels;
}
}
XAML:
<UserControl x:Class="UCM.WFDesigner.Views.UserTextMainView"
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:model="clr-namespace:Cellebrite.Diagnostics.Model.Entities;assembly=Cellebrite.Diagnostics.Model"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:converters1="clr-namespace:UCM.Infra.Converters;assembly=UCM.Infra"
xmlns:core="clr-namespace:UCM.WFDesigner.Core"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<DockPanel>
<StackPanel Orientation="Horizontal"
DockPanel.Dock="Top"
HorizontalAlignment="Left">
<DockPanel>
<TextBlock Text="Search:"
DockPanel.Dock="Left"
VerticalAlignment="Center"
FontWeight="Bold"
Margin="0,0,5,0" />
<Button Style="{StaticResource StyleButtonDeleteCommon}"
Height="20"
Width="20"
DockPanel.Dock="Right"
ToolTip="Clear Filter"
Command="{Binding ClearSearchCommand}" />
<TextBox Text="{Binding SearchInput, UpdateSourceTrigger=PropertyChanged}"
Width="500"
VerticalContentAlignment="Center"
Margin="0,0,2,0"
FontSize="13" />
</DockPanel>
</StackPanel>
<Grid>
<DataGrid ItemsSource="{Binding Path=TranslationsView}"
AutoGenerateColumns="False"
SelectionMode="Single"
CanUserAddRows="True">
<DataGrid.Columns>
<!-- your columns definition is here-->
</DataGrid.Columns>
</DataGrid>
<!-- your "busy indicator", that shows to user a message instead of stuck data grid-->
<Border Visibility="{Binding IsBusy,Converter={converters1:BooleanToSomethingConverter TrueValue='Visible', FalseValue='Collapsed'}}"
Background="#50000000">
<TextBlock Foreground="White"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="Loading. . ."
FontSize="16" />
</Border>
</Grid>
</DockPanel>
This pattern allows to work with data grid in a quite simple way and code is very simple either.
Do not forget to create default constructor for class that represents your data source.