I have 3 data tables:
User
UserID, UserName
1, Bat
2, John
etc...
Member
MemberID, Member
1, Local
2, Zone
etc...
UserMember
UserID,MemberID
1, 1
1, 2
2, 1
etc...
On the user input XAML Form in WPF
Check list box is bound to the Member table.
My Question:
I want it so that when the user checks the member type, the selected values automatically gets inserted into the UserMember table. And when the the user unchecks member type, the selected values are deleted from the UserMember table.
How can I do this in WPF?
It depends on what kind of software methodology you want to use (I like MVVM with WPF) what kind of ORM(Linq, EF, Castle, etc.).
your could create a ViewModel like so:
public class UserViewModel
{
int UserId {get;set;}
Collection<Member> MemberList {get;set;}
}
public class Member
{
bool _IsMember;
bool IsMember
{get return _IsMember;set _IsMember = value; EditMemberStatus();}
int MemberId {get;set;}
string Member {get;set;}
void EditMemberStatus()
{
if (IsMember)
//Code to add row into db using your ORM choice
else
//Code to remove row from db
}
}
in your xaml you could do this:
<ListView ItemsSource={Binding MemberList}>
<ListView.View>
<GridView>
<GridViewColumn Header="IsMember"}>
<CheckBox IsChecked="{Binding IsMember}"/>
</GridViewColumn
<GridViewColumn Header=Member DisplayMemberBinding={Binding Member}/>
</GridView>
</ListView.View>
</ListView>
And finally in the code behind of your xaml you have this
public class UserView
{
UserView()
{
InitializeComponent();
DataContext= new UserViewModel();
}
}
This is just a skeleton, hope this helps. It's also only one way of a myriad of ways to do it.
Related
I made a simple example playing with MVVM. I have a simple class Person in Models, then a class PersonsViewModel has a list of Person. I'm doing so by install Prism through Nuget and raise the event PropertyChanged in the collection of people.
private ObservableCollection<Person> people;
public ObservableCollection<Person> People
{
get { return people; }
set
{
people = value;
this.RaisePropertyChanged("People");
}
}
then I bind it to a datagrid
<DataGrid Grid.Row="1" x:Name="dataGrid" AutoGenerateColumns="False" ItemsSource="{Binding People}" >
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName, Mode=OneWay}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName, Mode=OneWay}"/>
<!--<DataGridTextColumn Header="Sex" Binding="{Binding Sex, Mode=TwoWay}"/>-->
<DataGridComboBoxColumn Header="Sex" SelectedItemBinding="{Binding Sex, Converter={StaticResource sexTypeConverter}}" ItemsSource="{Binding Source={StaticResource sexType}}"/>
<DataGridTextColumn Header="Score" Binding="{Binding Score, Mode=TwoWay}"/>
</DataGrid.Columns>
</DataGrid>
it works find so far, i can insert or remove line, change the quantity in the UI and reflect to the viewmodel.
But the problem is that if I change the quantity in the code, it won't show in the form until I double click into it.
here is the screenshot when my app starts, i've added three records with different scores.
enter image description here
in my code, I've put a command as below, so basically everytime I run the command it will increase the score by 20 for each person. Which it does. the problem is that after I click the button, the score on the form doesn't change, only when i double click into the field score, I can see the number is actually changed.
private void IncQty()
{
foreach (Person item in this.People)
{
item.Score += 20;
}
}
I've tried to search online, some say that I should bind the datasource again. But isn't ObservableCollection already implements the INotifyPropertyChanged and my view should talk to my viewmodel everytime something is changed?
Also ideally I shouldn't do anything in my ViewModel to manipulate the element in View, it seems to break this rule if I write some code to attach the bind again in viewmodel.
Please help. Thanks a lot.
The object contained within your observablecollection needs to implement INotifyPropertyChanged. Without it none of the UI components (and the ObservableCollection itself) can know something has changed and therefore need to refresh.
e.g.:
public abstract class ObservableBase : INotifyPropertyChanged
{
private PropertyChangedEventHandler _notifyPropertyChanged;
public event PropertyChangedEventHandler PropertyChanged
{
add { this._notifyPropertyChanged = (PropertyChangedEventHandler)Delegate.Combine(this._notifyPropertyChanged, value); }
remove { this._notifyPropertyChanged = (PropertyChangedEventHandler)Delegate.Remove(this._notifyPropertyChanged, value); }
}
protected void OnPropertyChanged(string property)
{
if(this._notifyPropertyChanged != null)
{
this._notifyPropertyChanged.Invoke(property);
}
}
}
public class Person : ObservableBase
{
private int _score;
public int Score
{
get => this._score;
set
{
if(this._score != value)
{
this._score = value;
// would be better if mixed with reflection and Expression
// so you can this.OnPropertyChanged(p => p.Score);
// as per Rockford Lhotka & CSLA
this.OnPropertyChanged("Score");
}
}
}
}
As a result when you set the score property, property changed will fire, which should mean the datarow component it is bound to will hear it. Even if not the observable collection listens to see if it's T is also INotifyPropertyChanged and will fire an event of its own letting the UI know that the item has changed. Either way your WPF will now reflect the changed applied to score.
I have a series of UserControls intended for printing purposes only. They are mainly tables implemented with ListView & GridView. Each control has a backing view model, and the ItemSource of the ListView is bound to the view model. All pretty standard. When I actually print these controls to PDF, they display the bound data without an issue. The trouble arises when I am trying to Measure and Arrange the individual controls so that I can determine how many rows will fit on that page.
<UserControl>
<Grid>
<ListView x:Name="ItemsListView" ItemsSource="{Binding Items}">
<ListView.View>
<GridView>
<GridView.Columns>
<GridViewColumn Header="Column Name 1" DisplayMemberBinding="{Binding Column_Property_1}" />
<GridViewColumn Header="Column Name 2" DisplayMemberBinding="{Binding Column_Property_2}" />
</GridView.Columns>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
public partial class MyControl : UserControl
{
private readonly ViewModel _viewModel;
public MyControl()
{
InitializeComponent();
}
public MyControl(ViewModel viewModel) :this()
{
_viewModel = viewModel;
this.DataContext = _viewModel;
}
}
public class ViewModel
{
public IEnumerable<MyItem> Items { get; set; }
}
public class MyItem
{
public string Column_Property_1 { get; set; }
public string Column_Property_2 { get; set; }
}
When I Measure and Arrange the control in the pagination service, the ActualHeight of the control is always { 0, 0 }.
public Size GetMeasuredSize(UserControl control)
{
control.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
control.Arrage(new Rect(control.DesiredSize));
control.UpdateLayout();
return new Size(control.ActualWidth, control.ActualHeight);
}
What I have found though, by inspecting the control in the debugger during the GetMeasureSize method, is that the Items property on the ListView control is not populated at all by the binding- the count is 0. So it makes sense that its desired size and actual sizes are 0, it just doesn't have any items to dispay.
If I remove the binding and manually add each item from my view model, it's a completely different story, and I get the correct ActualWidth and ActualHeight from the control.
public MyControl(ViewModel viewModel) :this()
{
_viewModel = viewModel;
this.DataContext = _viewModel;
foreach (var item in _viewModel.Items)
{
ItemsListView.Items.Add(item);
}
}
Obviously, that is a little less than desirable. So I'm hoping that someone can either help explain this behavior and point me in the right direction to get this working with the bindings.
I've tried "force updating" the binding in the MeasureOverride method
ItemsListView.GetBindingExpression(ListView.ItemsSourceProperty).UpdateTarget();
BindingOperations.GetBindingExpression(ItemsListView, ListView.ItemsSourceProperty).UpdateTarget();
Neither resulted in the items property being populated.
Update:
I've just found this:
Customized Measure / Arrange algorithms on controls that use Data Binding in WPF
which sheds some light on the situation.
in my WPF application, I have a DataGrid, which is bound to an ObservableCollection.
<DataGrid x:Name="DataGridTeilnehmer" HorizontalAlignment="Left" VerticalAlignment="Top" CellEditEnding="DataGridTeilnehmer_CellEditEnding" AutoGenerateColumns="False" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Teilnehmer" CellEditingTemplate="{StaticResource TeilnehmerEditTemplate}" CellTemplate="{StaticResource TeilnehmerCellTemplate}" />
<DataGridComboBoxColumn Header="Pass" />
...
The DataGridComboBoxColumn shall be filled with individual values for each row. The values depend on the entry of the first column. So, I would like to set the data in the CellEditEnding event like this:
private void DataGridTeilnehmer_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
if (!commiting)
{
commiting = true;
DataGridTeilnehmer.CommitEdit(DataGridEditingUnit.Row, false);
commiting = false;
// check, whether it is the first column that has been edited
if (...)
// get the list<string> for the combobox depending on the edited content
// get the combobox of the current row and bind the calculated list<string> to it
}
}
}
How can I do this?
EDIT: An example of what I am trying to achieve.
I have list of customers, which have individual tickets each. When the customer has been chosen in the first column, I want to load the ticket-list this customer has and load it into the next column - the combobox column.
Thanks in advance,
Frank
If you bound your datagrid to an ObservableCollection and your object implements INotifyPropertyChanged you can achieve what you need without using cell editending event.
In your model just check the value of your first column then set others columns values:
private string _firstColumn;
public string FirstColumn
{
get { return _firstColumn; }
set {
_firstColumn = value;
if(value == ...)
//set other properties
...
//notify the change
OnPropertyChanged("FirstColumn"); }
}
when your datagridrow lost focus all the new values will be notified to the datagrid
After researching a solution to this for three days, finally I gave up. Now I need your help to solve this.
Scenario:
I've a GridView in a Usercontrol (Lets say WLMSLogs.xaml) and My GridView ItemSource is binded to a List from the ViewModel (WMLSLogsViewModel.cs)
Lets say the List has 4 items (EventID, Name, Request and Response). BothRequest and Responses are XML Strings.
GridView needs to display some of the List items in RowDetailsTemplate under different tab items. So I'm displaying Request and Response under respective TabItems.
<GridView x:Name="WMLSLogGrid" ItemsSource="{Binding WMLSLogModelList}">
<GridView.Columns>
<GridViewDataColumn DataMemberBinding="{Binding ID}" Header="ID"/>
<GridViewDataColumn DataMemberBinding="{Binding UserName}" Header="UserName"/>
</GridView.Columns>
<GridView.RowDetailsTemplate>
<DataTemplate >
<TabControl>
<TabItem Header="Request Xml">
<TextBlock text="{Binding Request}"/>
</TabItem>
<TabItem Header="Response Xml">
<TextBlock text="{Binding Response}"/>
</TabItem>
<TabItem Header="EventLogs" Tag="{Binding .}">
<views:LogEvents />
</TabItem>
</TabControl>
</Grid>
</DataTemplate>
</GridView.RowDetailsTemplate>
WMLSLogModelList is a Observable collection in WMLSLogsViewModel.cs
So far everything works fine and Grid is displaying data as expected.
Now When User expands any row, he can see two TabItems with request and response.
Here I need to add one more TabItem (LogEvents) besides Request and Response tabs.
This LogEvents tab is going have one more GridView to display (so I added a new View <views:LogEvents /> in the tab). Here comes the tricky part.
This LogEvents GridView needs to get the data based on the corresponding Selecteditem (which is EventId), and pass this EventId to a different ViewModel (LogEventViewModel.cs) and binds the data to the Inner GridView dynamically. All this has to happen either as I expand the RowDetails section or if I select the Tab LogEvents.
There is no relation between the data items of these two Grids, except getting the Selected EventId of the main GridView and passing this to a different ViewModel then to Domain service to get the inner GridView Data.
What I did so far
As I mentioned I created a new View UserControl for LogEvents, Placed it under new TabItem(EventLogs) inside row details template of main GridView.
LogEvent UserControl contains a Grid View binded to LogEventsViewModel to get the Collection based on Selected row EventId.
How do I assign the Selected EventId to a new ViewModel and Get the data dynamically?
One Way: As I showed you, I called LogEvents by placing it in side TabItem. whenever I expanded any row, Then It is Initializing the LogEvents page, During that I tried to bind the Datacontext to LogEventsViewModel. But I'm unable to get the Seleted row EventId dynamically. If I get that then I can easily pass it to the LogEventsViewModel constructor.
var _viewModel = new LogEventsViewModel(EventId);
this.DataContext = _viewModel;
InitializeComponent();
Other way:
Pass the selected EventId directly from xaml View binding to that page initialization and then to LogEventsViewModel
Something like this
<views:LogEvents something="{Binding EventId}"/>
Is there any other way to do this?
Details:
LogEvents.xaml
<UserControl.Resources>
<viewModel:LogEventsViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource ViewModel}}">
<GridView x:Name="LogEventsGrid" ItemsSource="{Binding View}"
<GridView.Columns>
<telerik:GridViewToggleRowDetailsColumn />
<telerik:GridViewDataColumn DataMemberBinding="{Binding EventId}" Header="LogEventId"/>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Exception}" Header="Exception" />
</GridView.Columns>
</telerik:RadGridView>
</Grid>
LogEvents.xaml.cs
public int EventId
{
get { return (int)GetValue(EventIdProperty); }
set { SetValue(EventIdProperty, value); }
}
public static readonly DependencyProperty EventIdProperty =
DependencyProperty.Register("EventId", typeof(int), typeof(ApplicationLog),
new PropertyMetadata(0, new PropertyChangedCallback(OnEventIdChanged)));
private static void OnEventIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
int LogEventId1 = (int)e.NewValue;
// Need to assign propery in LogEventsViewModel
}
LogEventsViewModel.cs
WMLCDomainContext _Context = new WMLCDomainContext();
private QueryableDomainServiceCollectionView<LogEvents> view;
public int _eventid;
public ApplicationLogsViewModel()
{
EntityQuery<LogEvents> getLogEventsQuery = _Context.GetApplicationLogListQuery(EventId);
this.view = new QueryableDomainServiceCollectionView<ApplicationLog>(_Context, getLogEventsQuery );
}
public IEnumerable View
{get {return this.view;}}
public int EventId
{
get{return this._eventid;}
set{_eventid = value;}
}
I would create a dependency property on your LogEventsViewModel and then set up a binding on your LogEvents view, something like this:
<views:LogEvents EventId="{Binding EventId}" />
Then in LogEvents.xaml.cs you could create your dependency property:
private LogEvents_ViewModel _viewModel
{
get { return this.DataContext as LogEvents_ViewModel; }
}
public string EventId
{
get { return (string)GetValue(EventIdProperty); }
set { SetValue(EventIdProperty, value); }
}
public static readonly DependencyProperty EventIdProperty =
DependencyProperty.Register("EventId", typeof(string), typeof(LogEvents),
new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnEventIdChanged)));
private static void OnEventIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((LogEvents)d).OnTrackerInstanceChanged(e);
}
protected virtual void OnEventIdChanged(DependencyPropertyChangedEventArgs e)
{
this._viewModel.EventId = e.NewValue;
}
KodeKreachor is correct, though it may be necessary to set a Bindable attribute on your exposed property. Without it, a property might not always show in the bindable properties of the control, even if it does still work.
Good morning,
Apologies for the mass of text im about to provide but...
I have a WPF ListView with its ItemsSource bound to an ObservableCollection in it's respective ViewModel. When the window loads, the observable collection is populated from a web service by means of a Command. However, as the program is running, this collection is periodically updated by a BackgroundWorker thread in order to add new items to the ObservableCollection.
This mechanism works fine. The ListView is updated without issue on both the UI thread and the background thread. However, when an item in the ListView is double clicked, a new window is opened to display details of the Ticket object contained within the aforementioned ObservableCollection.
I have a private method which fires each time the ObservableCollection's set method is called which serves to find the Ticket item from within the collection which has been opened in the new window and update its properties according to the items in the newly updated ObservableCollection. Before doing this update, I check to ensure the ObservableCollection.Count is greater than 1, there is no point doing an update if there is nothing to update from!
My issue is that the ObservableCollection.Count property ALWAYS equates to 0. But I know this not to be true as the ListView is still updating its items with new Ticket objects added to this collection, if the count of this collection really was 0, then this would be reflected by the ListView also having no items in it as it is bound to this collection.
So what is going on here ? Im wondering maybe because the BackgroundWorker is calling;
myCollection = new ObservableCollection();
on a different thread to the UI that when I check the count on the UI thread, the wrong collection object is actually tested for 'Count'. But this still doesn't explain why the ListView reflects the contents of the ObservableCollection without issue.
Again, apologies for the wall-o-text but I wanted to explain this issue fully.
Thank you for your time and any input you may give.
EDIT FOR MORE DETAIL
The list view section of user control
<ListView x:Name="lvTicketSummaries" ItemsSource="{Binding Path=TicketSummaries}" Grid.Row="1" Width="Auto" Height="Auto" SizeChanged="lvTicketSummaries_SizeChanged" SelectionMode="Single" Foreground="Black" Background="#3BFFFFFF" ItemContainerStyle="{DynamicResource ListViewItem}">
<ListView.View>
<GridView AllowsColumnReorder="True">
<GridViewColumn Header="ID"
DisplayMemberBinding="{Binding ID}"
Width="25"/>
<GridViewColumn Header="Status"
DisplayMemberBinding="{Binding Status}"
Width="25"/>
<GridViewColumn Header="Subject"
DisplayMemberBinding="{Binding Subject}"
Width="25"/>
<GridViewColumn Header="Requester"
DisplayMemberBinding="{Binding Owner.Name}"
Width="25"/>
</GridView>
</ListView.View>
</ListView>
The view model of the above user control
Here you see the TicketSummaries collection which the list view is bound to as well as the refreshOpenTicket() method used to update the Ticket property in a child view model which the new instance of itself in the newly refreshed collection.
public class MainWindowViewModel : ViewModelBase
{
private DispatcherTimer timer;
private BackgroundWorker worker_TicketLoader;
private ObservableCollection<Ticket> ticketSummaries;
public ObservableCollection<Ticket> TicketSummaries
{
get { return ticketSummaries; }
set
{
ticketSummaries = value;
this.RaisePropertyChanged(p => p.TicketSummaries);
refreshOpenTicket();
}
}
private void refreshOpenTicket()
{
// Check there are actually some tickets to refresh
if (TicketSummaries.Count < 1)
return;
// Check we have created the view model
if (TicketDetailsViewModel != null)
{
// Check the ticket loaded correctly
if (TicketDetailsViewModel.Ticket != null)
{
// Find a ticket in the collection with the same id
Ticket openTicket = TicketSummaries.Where(
ticket => ticket.ID == TicketDetailsViewModel.Ticket.ID
).First();
// Make sure we are not going to overrite with a null reference
if (openTicket != null)
TicketDetailsViewModel.Ticket = openTicket;
}
}
}
This collection is updated from various sources via the following command
private void Execute_GetAgentsTickets(object agent)
{
TicketSummaries = new ObservableCollection<Ticket>();
var agentsTickets = ticketService.GetAgentsTickets((Agent)agent);
agentsTickets.ForEach(
ticket => TicketSummaries.Add(ticket)
);
AppSettings.LoggedAgent = (Agent)agent;
RequeryCommands();
}
But occasionally this collection will be modified off-thread by the background worker
void worker_TicketLoader_DoWork(object sender, DoWorkEventArgs e)
{
State = "Loading Tickets";
IsLoadingTickets = true;
var agentsTickets = ticketService.GetAgentsTickets(AppSettings.LoggedAgent);
TicketSummaries = new ObservableCollection<Ticket>();
foreach (Ticket ticket in agentsTickets)
{
TicketSummaries.AddOnUIThread<Ticket>(ticket);
}
refreshOpenTicket();
lastRefresh = DateTime.Now;
}
Just in case it makes a difference, the TicketSummaries.AddOnUIThread(ticket); is a solution I found on StackOverflow to trying to add items to a collection which is a binding source to UI controls off-thread and is as;
public static void AddOnUIThread<T>(this ICollection<T> collection, T item)
{
Action<T> addMethod = collection.Add;
Application.Current.Dispatcher.BeginInvoke(addMethod, item);
}
I hope this helps shed some more light on the situation.
Thanks again for your time.
You haven't provided enough information to diagnose, but I'm guessing the ListView is bound to a property and when you replace the ObservableCollection, you're not updating that property. Hence, the ListView is still attached to the original collection whilst your VM code is working with a new one.
Why replace the OC at all? Why not just update it as items come through from the middle tier? If you really must replace it, be sure to go via a property and raise change notification for that property.