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.
Related
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));
}
}
}
}
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>
UPDATE 3
" I want that the text from this TextBox should be shown in another TextBox(in another view)"
The second textbox in another view is meant to show other information that is tied to the first textbox, but not the copy.
So the user control contains a text box for, say, Bus code. Once I enter bus code, tabbing out will trigger a fetch from the database for other details such as bus name, bus destination, bus model etc.
The others textbox which is in another view then displays the bus name. All following textboxes display destination and so forth. When the command is invoked, and I try to write to the property BusName, it gets assigned (and I call Notify("BusName")) but it does not show on the UI.
Hope that was more clear. Sorry for any confusion caused :).
UPDATE2 - Response to blindmeis
Thanks for your reply though this not appear to be what I was looking for. The tab out is essential because that is how management wants their pages to be populated i.e. when you tab out of a 'code' textbox after entering the code, it will use the code to fetch data from the db to populate the rest of the controls. This does not appear to have the tab-out behavior in it. As for the 3rd dependency property, it is in my original code, I simply did not include it here because the value in the first textbox (user control tabout textbox) is not relevant to the problem. Simply, what I am trying to accomplish is that the second textbox must populate when you tab-out of the first textbox.
I could do this with an eventhandler, but wanted to use commands. I am thinking now perhaps commands are not the way to go here and I should switch to using an event handler.
Please advise if you still have any ideas on how to get the second textbox to populate when you tab out of the first (by putting a breakpoint in populate, you will see that the property gets assigned. ). If I have not understood correctly or missed something here, please let me know. Thanks!
UPDATE!
I have created a VS2013 solution mimicking my code, which reproduces the problem. It is at this public google drive link as a zip file (takes a few seconds for the download icon to appear):
https://drive.google.com/file/d/0B89vOvsI7Ubdbk85SVlvT3U2dVU/view?usp=sharing
You will see that the 2nd text box does not update despite the bound property storing the new value.
Greatly appreciate any help. Thanks in advance.
Original post:
I have a textbox control to which I have tied a key binding based command to go process some actions (in a method that the command has been tied to) when the user hits tab while in the textbox (tabs out).
I have other controls in that page that are boiund to properties in the viewmodel that I write to in that tab-out connected function. When I write my properties in the constructor or somewhere 'outside' that command invokation they seem to work fine and the values show on the page, but when I write them within that command invocation, the properties in the vm contain the values but don't show up on the UI
Any ideas why and how to fix?
Thanks much in advance
From XAML:
<TextBox Name="txtCode" Text="{Binding Path=CodeValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Key="Tab" Command="{Binding RetrieveRecordCmd}" > </KeyBinding>
</TextBox.InputBindings>
</TextBox>
From VM:
RetrieveRecordCmd = new GSSCommand(RetrieveRecord, param => this.CanExecuteRetrieveRecordCmd);
Command tied function:
public void RetrieveRecord(object obj)
{
objPie = null;
//Check if a record exists for that code
gssSvcMethodStatusBase = gssSvcClientBase.ReadPies(ref gssSvcGlobalVarsBase, out objPie, out grfaBase, CodeValue);
if ((objPie != null)) // && (objPie.DateCreated > DateTime.MinValue))
PopulatePage(objPie);
else if (objPie == null)
InitiateCreateNew();
else
return;
}
It looks like you have implemented the INotifyPropertyChanged interface in the strict sense, but are missing the actual functionality. The interface itself doesn't automatically give you change notifications. You also need to fire the PropertyChanged event when each property changes. The standard pattern looks like:
private string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
return;
_name = value;
NotifyPropertyChanged("Name");
}
}
You should make a habit of writing all mutable properties which you intend to bind to the UI in this format. Snippets can make this easier to do consistently.
this works, but i dont know if this is the behavior you want.
<UserControl x:Class="ProblemDemoWPF.TextBoxTabOutUserControl"
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"
mc:Ignorable="d"
Name="TabOutTextUserControl"
>
<StackPanel Margin="0,0,0,0" Orientation="Horizontal">
<Label Content="UserControl (ucTextBox)->"></Label>
<TextBox Width="80" Height="30" BorderBrush="Black"
BorderThickness="2"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding ElementName=TabOutTextUserControl, Path=CodeValue}">
</TextBox>
</StackPanel>
</UserControl>
new Dependency Propertie with right binding without DataContext
public partial class TextBoxTabOutUserControl : UserControl
{
public static readonly DependencyProperty CodeValueProperty =
DependencyProperty.Register("CodeValue", typeof(string), typeof(TextBoxTabOutUserControl));
public string CodeValue
{
get { return (string)GetValue(CodeValueProperty); }
set { SetValue(CodeValueProperty, value); }
}
public TextBoxTabOutUserControl()
{
InitializeComponent();
}
}
Just bind both to LocTextBoxText
<Window x:Class="ProblemDemoWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:ProblemDemoWPF"
Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<loc:TextBoxTabOutUserControl Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="left"
CodeValue="{Binding Buscode, Mode=OneWayToSource}"/>
<Label Content="Busname" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="1" Grid.Column="1" Text="{Binding Path=Busname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Label Content="Busdestination" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="2" Grid.Column="1" Text="{Binding Path=Busdestination, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Label Content="Busmodel" Grid.Row="3" Grid.Column="0" HorizontalAlignment="Left"></Label>
<TextBox Width="100" Grid.Row="3" Grid.Column="1" Text="{Binding Path=Busmodel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
</Grid>
</Window>
add Notify to property setter
class MainWindowViewModel : INotifyPropertyChanged
{
private String _buscode;
private string _busname;
private string _busdestination;
private string _busmodel;
public String Buscode
{
get
{
return _buscode;
}
set
{
if (_buscode != value)
{
_buscode = value;
Notify("Buscode");
FetchData(_buscode);
}
}
}
private void FetchData(string buscode)
{
//DB stuff
this.Busname = "Name 1234";
this.Busmodel = "Model 1234";
this.Busdestination = "Destination 1234";
}
public string Busname
{
get { return _busname; }
set { _busname = value; Notify("Busname"); }
}
public string Busdestination
{
get { return _busdestination; }
set { _busdestination = value; Notify("Busdestination"); }
}
public string Busmodel
{
get { return _busmodel; }
set { _busmodel = value; Notify("Busmodel"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Trying to understand this better.
I have an ItemsControl defined in my mainview something like this
<ItemsControl Grid.Column="0" Grid.Row="2"
ItemsSource="{Binding Notes}"
ItemTemplate="{Binding Source={StaticResource MyParagraph}}"
>
</ItemsControl>
in which I would like to use a DataTemplate:
<UserControl.Resources>
<DataTemplate x:Key="MyParagraph">
<v:InkRichTextView
RichText="{Binding ?????? "
</DataTemplate>
</UserControl.Resources>
The InkRichTextView is a view with a dependency property, RichText, being used to pass a paragraph from the ObservableCollection(InkRichViewModel) Notes in the mainview to the user control. That is, this works correctly for one paragragh:
<v:InkRichTextView RichText ="{Binding Path=Note}" Grid.Column="0" Grid.Row="0" />
where Note is defined as a paragraph in the MainView.
The problem is, how do I write the DataTemplate and the ItemsControl such that the ItemsControl can pass each paragraph from the observablecollection to the dependency property RichText in the InkRichTextView?
Thanks for any guidance.
(I hope this is understandable!)
Items control:
<ItemsControl x:Name="NotesItemsControl" Grid.Column="2" HorizontalAlignment="Center">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<local:InkRichTextView RichText="{Binding Note}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code behind:
class InkRichViewModel : System.ComponentModel.INotifyPropertyChanged
{
#region Note (INotifyPropertyChanged Property)
private string _note;
public string Note
{
get { return _note; }
set
{
if (_note != value)
{
_note = value;
RaisePropertyChanged("Note");
}
}
}
#endregion
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string p)
{
var propertyChanged = PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(p));
}
}
}
public MainWindow()
{
InitializeComponent();
var item01 = new InkRichViewModel() { Note = "Note 01", };
var item02 = new InkRichViewModel() { Note = "Note 02", };
var item03 = new InkRichViewModel() { Note = "Note 03", };
var item04 = new InkRichViewModel() { Note = "Note 04", };
var item05 = new InkRichViewModel() { Note = "Note 05", };
var itemList = new List<InkRichViewModel>()
{
item01, item02, item03, item04, item05,
};
NotesItemsControl.ItemsSource = itemList;
}
How it looks at runtime:
Is that what you're looking for?
Based on what you describe, it seems that each item in your ItemsControl is a paragraph, the very object you want to assign to the InkRichTextView.RichText property. Is that correct?
If so, keep in mind that within the item template, the data context is the collection item itself - thus, the path you are looking for does not refer to a property of the data context, but to the data context itself.
That is done with the dot (.) path:
<v:InkRichTextView RichText="{Binding .}"/>
I'm posting this as an answer, although the credit goes to O.R.Mapper and Murven for pointing me in the right direction. My post is to help anyone else just learning this.
In very simple terms, the ItemControl performs a looping action over the collection in its ItemsSource. In my case the ItemsSource is a collection of type InkRichViewModel. (Hence the question from Murven). In its looping action, the ItemsSource will create objects from the InkRichViewModel. (Thus, my usercontrol now has an individual datacontext!) Each of these objects will use the ItemTemplate for display. So to simplify things, I moved the DataTemplate from the UserControl Resources to within the ItemControl itself as:
<ItemsControl Grid.Column="0" Grid.Row="2"
ItemsSource="{Binding Notes}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<v:InkRichTextView RichText="{Binding Note}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now that each of my usercontrols has its own datacontext being assigned by the ItemsControl, the Output window (VS2010) now shows the binding errors. Fixing these errors leads to a working solution.
Hope this helps other newbies like myself. Thanks everyone.
(Ooops! Just saw the answer from Murven but I'll leave this if it helps somebody to understand.)
I already have a working ListBox with Items from my local database. Now I wanted to upgrade this to a CollectionViewSource for filtering. After my upgrade the new ListBox with CollectionViewSource shows nothing.
MainPage Code Behind:
// Data context for the local database
private BuildingDataContext toDoDB;
// Define an observable collection property that controls can bind to.
private ObservableCollection<Building> _buildings;
public ObservableCollection<Building> BuildingTable
{
get
{
return _buildings;
}
set
{
if (_buildings != value)
{
_buildings = value;
NotifyPropertyChanged("BuildingTable");
}
}
}
public CollectionViewSource Source { get; set; }
// Konstruktor
public MainPage()
{
InitializeComponent();
// Connect to the database and instantiate data context.
toDoDB = new BuildingDataContext(BuildingDataContext.DBConnectionString);
// Data context and observable collection are children of the main page.
this.DataContext = this;
}
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// Define the query to gather all of the to-do items.
var toDoItemsInDB = from Building todo in toDoDB.BuildingTable
select todo;
// Execute the query and place the results into a collection.
BuildingTable = new ObservableCollection<Building>(toDoItemsInDB);
Source = new CollectionViewSource();
Source.Source = BuildingTable;
// Call the base method.base.OnNavigatedTo(e);
}
For that purpose I added the lines:
public CollectionViewSource Source { get; set; }
Source = new CollectionViewSource();
Source.Source = BuildingTable;
I tried as well to put
Source = new CollectionViewSource();
Source.Source = BuildingTable;
in my MainPage Constructor. It doesnt work as well.
My Mainpage.xaml:
<!--<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding BuildingTable}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">-->
<ListBox x:Name="toDoItemsListBox" ItemsSource="{Binding Source.View}" Grid.Row="0" Margin="12, 0, 12, 0" Width="440" SelectionChanged="goToNavigation">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" Width="440">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Name="textBlockShortcut" Text="{Binding Shortcut}" Width="Auto" HorizontalAlignment="Left" Grid.Column="0" Margin="0,0,0,5" FontSize="36" />
<TextBlock Name="textBlockName" Text="{Binding BuildingName}" Width="Auto" HorizontalAlignment="Left" Grid.Column="1" Margin="0,0,0,5" FontSize="36" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The first commented line shows the old working listbox without CollectionViewSource. So what am I missing?
EDIT:
private void goToNavigation(object sender, RoutedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (toDoItemsListBox.SelectedIndex == -1)
return;
// Navigate to the new page
PhoneApplicationService.Current.State["SelectedItem"] = toDoItemsListBox.SelectedItem;
NavigationService.Navigate(new Uri("/NavigationPage.xaml", UriKind.Relative));
// Reset selected index to -1 (no selection)
toDoItemsListBox.SelectedIndex = -1;
}
You would usually create and bind to a CollectionViewSource in XAML:
<UserControl.Resources>
<CollectionViewSource x:Key="cvs"/>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource="{Binding Source={StaticResource cvs}}" ...>
...
</ListBox>
</Grid>
and in code-behind just access that CollectionViewSource like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
...
var cvs = Resources["cvs"] as CollectionViewSource;
cvs.Source = BuildingTable;
}
You don't use the CollectionViewSource class directly, you use a CollectionView of the appropriate type.
View = CollectionViewSource.GetDefaultView( myCollection );
and then you bind that directly to your source.
ItemsSource="{Binding View}"
You can and only should use a CollectionViewSource from xaml, because thats its main purpose. From code you should directly create a CollectionView or use the GetDefaultView method.