I am fairly new to WPF so am not really sure what I am doing wrong here. I want to the current time (Datetime.Now) to be displayed and updated to a label on a window. The time would be called to update in a method that loads data from a database (like a "last updated" idea). This methoud is called every 2 minutes (kept by a timer on a thread) once the user is logged in. I have the following class for the time object...
public class UpdatingTime : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private DateTime _now;
public UpdatingTime()
{
_now = DateTime.Now;
}
public DateTime Now
{
get { return _now; }
private set
{
_now = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Now"));
}
}
}
public void Update()
{
Now = DateTime.Now;
}
}
In the window's class constructor, I have...
UpdatingTime updateTime = new UpdatingTime();
lastUpdate.DataContext = updateTime;
In the method that loads the data from the database, I call the Update() method by...
updateTime.Update();
I think my data binding is the problem though (like I said, I'm pretty new). My label in the xaml file looks like...
<Label Name="lastUpdate" Margin="10" Height="auto" Content="{Binding Source updateTime, Path=Now}"
Visibility="Hidden" FontSize="20" />
The reason that visibility is hidden is because I set it to Visible once the user is logged in, I've tested that and I'm sure its not the issue. To be clear, the backend code is in the code behind file to the corresponding xaml file (eg Window.xaml, Window.xaml.cs), so I do not think I am missing a reference either.
The problem is that when I run the application, nothing at all displays (it compiles, and no exceptions are thrown). I am not sure what I am doing wrong, if anyone could shed some light on this I would greatly appreciate.
Also, if you could mention some good resources to learning and becoming familiar with WPF that you found helpful, that would be awesome. I am not sure what the DataContext really is and if I am using it correctly also.
Thanks.
Your Binding statement for the Content has a syntax error, and it is wrong.
should be:
Content="{Binding Now}"
or (identical):
Content="{Binding Path=Now}"
No need for 'Source' (and if you have multiple properties in Binding - you must have a comma).
For WPF resources, search in this site for [wpf] - the question with the most votes is a summary of resources: MVVM: Tutorial from start to finish?
Related
I am using the FastWPFGrid control found here:
https://github.com/FormatD/FastWpfGrid
I have looked at the sample applications and while there is a lot there to help with the implementation of the actual grid, I am at a bit of a loss as to how to get the binding to work. I am trying to bind the model of the fastgrid to a property of my datacontext - GridViewModel - this in turn is a view model which inherits from the fastgrid viewmodelbase.
So far, so good. The problem is that when the data changes, the notification isn't happening. See my simple example below. In this example, when I change the number of rows nothing happens. If I manually refresh the xaml (by changing the name of the binding to an invalid one and then back) it then updates. I just need to know how to trigger the notifypropertychanged from code.
I would appreciate a very simple example along the lines of the below:
My Xaml:
<fastWpfGrid:FastGridControl Grid.Row="1" Grid.Column="0" Model="{Binding SummaryVm}"/>
The main viewmodel (the datacontext of my form)
public SummaryGridViewModel SummaryVm{ get; set; }
// This Event fires when I know that the row count has changed (for example)
void OnRunListPropertyChanged(Message.RunListPropertyChanged obj)
{
// **This is where I need help to get the view to update
}
The Grid View Model
public class SummaryGridViewModel : FastGridModelBase
{
public SummaryGridViewModel(RunListCalculationQueueManager runList)
{
RunList = runList;
}
RunListCalculationQueueManager RunList { get; }
public override int ColumnCount => 15;
public override int RowCount =>
RunList.ActiveRun == null ? 0 : RunList.BaselineRun == null ? 1 : 3;
}
So the event in the main viewModel fires as expected, but no change is reflected in the view.
I am not necessarily looking for this code to be fixed, but a simple working example of what I am trying to do would be great.
Found the solution, it was pretty simple really -
FastGridModelBase exposes several notify methods. Calling
SummaryVM.NotifyRefresh
caused the notification to fire and everything works well.
I asked a similar question a while back here WPF MVVM User Control. I got some answers, but they were way off, so I guess I wasn't clear in what I want to do....
I am working on a WPF application using MVVM. The app is built using a component based approach, so I have some user controls that I've defined that will be used throughout the application. As an example, I have an Address control. I want to use it an multiple places throughout the application. Here's an example. See here:
http://sdrv.ms/1aD775H
The part with the green border is the Address control. The control has its own View Model.
When I place it on a window or other control, I need to tell it the PK of the customer to load addresses for. So I created a Customer ID DependencyProperty:
public partial class AddressView : UserControl
{
public AddressView()
{
InitializeComponent();
}
public static DependencyProperty CustomerIdProperty = DependencyProperty.Register("CustomerId", typeof(int), typeof(AddressView),
new UIPropertyMetadata(0, AddressView.CustomerIdPropertyChangedCallback, AddressView.CustomerIdCoerce, true));
public int CustomerId
{
// THESE NEVER FIRE
get { return (int)GetValue(CustomerIdProperty); }
set { SetValue(CustomerIdProperty, value); }
}
private static void CustomerIdPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
// THIS NEVER FIRES
AddressView instance = (AddressView)d;
instance.CustomerId = (int)args.NewValue;
}
enter code here
private static object CustomerIdCoerce(DependencyObject d, object value)
{
return value; // <=== THIS FIRES ON STARTUP
}
}
Then in the MainWindowView I have:
<vw:AddressView Grid.Row="1"
Grid.Column="0"
x:Name="AddressList"
CustomerId="{Binding ElementName=TheMainWindow, Path=SelectedCustomer.Id, Mode=TwoWay}"/>
Note my comments in the user control's CS. The Coerce fires on startup. The callback never fires, nor do the CustomerId getter or setter.
What I would like to happen seems simple, I just can't make it work....
When a customer is selected the CustomerId should be passed to the Address UserControl. Then in the VM for the Address UserControl should handle getting & saving the data.
So, again, 2 questions:
1) Anyone see what's wrong?
2) How does the UserControl DP send the PK to the ViewModel?
If anyone's interested, my sample project is here: http://sdrv.ms/136bj91
Thanks
Your CustomerId getter and setter will never fire in this situation. They are there simply as helper methods in case you want to access the CustomerIdProperty property from your code behind.
Your CustomerIdPropertyChangedCallback method will not fire because your binding expression is incorrect. You need to bind to the DataContext of MainWindow and not the window itself:
...
CustomerId="{Binding ElementName=TheMainWindow, Path=DataContext.SelectedCustomer.Id}"
...
Also, make sure that you are calling the INotifyPropertyChanged PropertyChanged event when the property bound to the ComboBox is changed.
Try this :
CustomerId="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}, Path=DataContext.YourSelectedItem.TheProperty}"
I am not sure how to manage your seleted item in window, so please change yourSelectedItem.TheProperty accordingly.
I want to use modules in my first WPF application, as I'm used to using them in WinForm applications I've created before. So I have this button, with a textblock inside.
MainWindow.xaml:
<Button x:Name="Connection_Button" Width="200" Height="30" Margin="5,5,5,5">
<TextBlock FontSize="14" MouseDown="TextBlock_MouseDown">CONNECT</TextBlock>
</Button>
Before in WinForms I could easily reference a button and it's text property by adding MainForm. before the control, but how can I do this in WPF through modules, similar to below? How do I even declare the controls at the top of my module code? And elements inside a control such as TextBlock?
Module_Connection.vb in my old WinForm way:
Private Sub Connect()
If MainForm.Connection_Button.Text = "Connect" Then
' Code
You don't usually do this in WPF.
To base your application logic in the state of UI elements is not really a good idea.
Why? because UI is Not Data and therefore you should base your application logic on the state of data items instead.
I suggest you read this and this
To try to use WPF the same way you use winforms is a straight path to miserable failure and suffering.
You must embrace MVVM and understand DataBinding and how the UI must always be separate from Application Logic and Data.
Edit: Adding a Code Sample (in C#)
ViewModel:
public class MainViewModel
{
public string ButtonContent {get;set;} //TODO: Add Property Change Notification
public MainViewModel()
{
ButtonContent = "SomeButtonContent";
}
}
View:
<Button Width="200" Height="30" Margin="5,5,5,5">
<TextBlock FontSize="14" Text="{Binding ButtonContent}"/>
</Button>
Code Behind:
public class MainWindow: Window
{
public MainWindow()
{
InitializeComponent();
SomeModule.Instance = new MainViewModel();
DataContext = SomeModule.Instance;
}
}
Module (static class in C#):
public static class SomeModule
{
public static MainViewModel Instance {get;set;}
public void DoSomething()
{
if (Instance.ButtonContent == "SomeButtonContent")
//...etc
}
}
This is what I mean by separating the UI from the data. You place your strings in a ViewModel, not in the View, then you evaluate the value of those ViewModel properties to determine what to do.
Still, basing your application logic on the value of a string seems like a weak solution. What do you really need to do? There are much better approaches such as using an Enum for this.
Edit2:
Having a ViewModel to Bind to does not "complicate" things. It actually simplifies them a LOT.
How? because you're now doing this with simple controls, but then you'll want to do the same thing with UI elements inside a ControlTemplate or even worse a DataTemplate and that's when real problems arise.
Therefore, you must get used to "the WPF Way" before you deal with more complex UI scenarios.
Non-optimal approach:
public class MainWindow: Window
{
public string ButtonContent
{
get
{
return this.txtButtonContent.Text;
}
set
{
this.txtButtonContent.Text = value;
}
}
}
<Button Width="200" Height="30" Margin="5,5,5,5">
<TextBlock x:Name="txtButtonContent" FontSize="14" Text="Connect"/>
</Button>
You must understand that the Button class doesn't have a Text property in WPF. In contrast to most ancient framweworks, WPF has a Content Model where literally anything can contain anything with little to no restrictions. Putting a Text property to the Button would be to introduce the limitation for the Button to only contain Text, which is not the case in WPF.
Therefore, what you actually want to do here is to modify the Text property of the TextBlock (which happens to be inside the Button, but could actually be anywhere in the Visual Tree).
That's why I mentioned the fact that you actually NEED a ViewModel to hold your data, because there is no (easy) way to access the UI elements located, for example within a DataTemplate in order to manipulate them. Your UI must always be a reflection of the state of your application's Data, which is stored in Model or ViewModel classes, not the UI.
This should get you in the ballpark:
Module:
Public Sub Connect(RyRef txtBox as TextBox)
If txtBox.Text = "...
note Public vs. Private
Call:
Call .Connect(MyTextBox1)
Call and are optional
I'll answer your question in VB.NET and not provide C# samples of alternative ways of doing it which if you're not used too can be an uphill struggle.
Declare this in your module, access the controls through rootWindow.
Private rootWindow As MainWindow = TryCast(Application.Current.MainWindow, MainWindow)
Another approach is :
Dim mw as MainWindow = Application.Current.Windows.OfType(Of MainWindow).First
assuming you have a MainWindow and only have one MainWindow.
I am having the hardest time figuring out a way to solve a problem I am having with databinding on a slider and a textbox.
The setup:
the current value of the slider is displayed inside of the textbox. When the user drags the slider the value is reflected inside the textbox. The user can choose to drag the slider and release to the value he chooses, click anywhere on the slider track to set the value or enter the value manually in the texbox. In the last case, the value entered in the textbox should update the slider position.
The texbox is two way bound to a datacontext property while the slider is one way bound to the same property. When the user slides or click on the slider tracker, I use the dragcompleted event of the slider to notify the datacontext of the modification. When the user clicks on the tracker on the other hand I use the OnValueChanged event of the slider to notify the datacontext (and use a flag to ensure the OnValueChanged was not triggered by a slider move)
The problem: The OnValueChanged event fires even when initializing the slider value with the binding value so I cannot figure out whether the value is actually coming from the user or the binding.
Could you please suggest maybe and alternative way to do the binding to ensure we can distinguish between user update and binding udpates for the slider?
Thnak you!
UPDATE Sorry I forgot to mention why I am not binding directly both slider and textbox two ways like the below answers suggest. The update to the data context value is supposed to trigger a call to a backend server and retrieve data from a database. The problem is that when the user drags the slider it constantly fires updates. I go around the problem by only relying to the actual onValueChanged event to call the DoWhatever method. I hope that's a bit clearer. Sorry for omitting this...
I quickly put together the sample below for you to give it a try.
The xaml:
<Window x:Class="SliderIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid HorizontalAlignment="Center"
VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Slider Name="slider" VerticalAlignment="Top"
ValueChanged="slider_ValueChanged"
Thumb.DragStarted="slider_DragStarted"
Thumb.DragCompleted="slider_DragCompleted"
Value="{Binding Count}"
Width="200"
Minimum="0"
Maximum="100"/>
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25"/>
</Grid>
The code behind:
using System.Windows;
namespace SliderIssue
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool _dragStarted;
public MainWindow()
{
InitializeComponent();
var item = new Item();
DataContext = item;
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
if (!_dragStarted)
{
var item = (Item)DataContext;
item.DoWhatever(e.NewValue);
}
}
private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
{
_dragStarted = true;
}
private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
_dragStarted = false;
var item = (Item) DataContext;
item.DoWhatever(slider.Value);
}
}
}
A simple data class:
using System.ComponentModel;
namespace SliderIssue
{
public class Item : INotifyPropertyChanged
{
private int _count = 50;
public int Count
{
get { return _count; }
set
{
if (_count != value)
{
_count = value;
DoWhatever(_count);
OnPropertyChanged("Count");
}
}
}
public void DoWhatever(double value)
{
//do something with value
//and blablabla
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
UPDATE
OK, now I see why you were trying to do it like that. I have a couple of suggestions that may help.
My first one is a bit more opinionated, but I offer it nonetheless. If the problem you are trying to solve is throttling requests to a back-end database, I would contend that your ViewModel need not concern itself with that. I would push that down a layer into an object that is making the call to the back-end based on the updated value passed down from the ViewModel.
You could create a poor-man's throttling attempt by recording DateTimeOffset.Now each time a call is made to the method to query the back-end DB. Compare that value to the last value recorded. If the TimeSpan between falls beneath your minimum threshold, update the value to which it was compared, and ignore the request.
You could do a similar thing with a timer and resetting the timer each time a request is made, but that is messier.
When the call returns from the back-end, this layer raises an event which the ViewModel handles and does whatever it needs to do with the data returned.
As another suggestion, I would also check out what the ReactiveExtensions give you. It takes a bit to kind of wrap your brain around how they work, but you could create an Observable from a stream of events, and then use the Throttle() method to return another Observable. You subscribe to that Observable and perform your call there. It would take more re-thinking the design and architecture of your software, but it is intriguing.
Paul Betts created an entire MVVM framework based around Rx called ReactiveUI. I first learned about throttling Observables in one of his blog posts here.
Good luck!
ORIGINAL POST
If I understand your problem correctly, it sounds like you would like both the Slider and the TextBox to reflect the same property of the DataContext (normally, a ViewModel). It looks like you are trying to duplicate what the binding mechanism of WPF gives you. I was able to get a quick prototype of this working. Here's the code I used.
For the view, I just created a new window with this as the content of the Window.
<StackPanel>
<Slider Value="{Binding TheValue}" Margin="16" />
<TextBox Text="{Binding TheValue}" Margin="16" />
</StackPanel>
Notice that both the Slider and the TextBox are bound to the same (cleverly-named) value of the DataContext. When the user enters a new value into the TextBox, the value will change, and the property change notification (in the ViewModel) will cause the slider to update its value automatically.
Here is the code for the ViewModel (i.e., the DataContext of the View).
class TextySlideyViewModel : ViewModelBase
{
private double _theValue;
public double TheValue
{
get { return _theValue; }
set
{
if(_theValue == value)
return;
_theValue = value;
OnPropertyChanged("TheValue");
}
}
}
My ViewModel is derived from a ViewModelBase class which implements the INotifyPropertyChanged interface. The OnPropertyChanged() method is defined in the base class which merely raises the event for the property whose name is passed as the parameter.
Lastly, I created the View and assigned a new instance of the ViewModel as its DataContext (I did this directly in the App's OnStartup() method for this example).
I hope this helps get you going in the right direction.
UPDATE:
Along the lines with Eric, but as a separate suggestion of operation.
Bind both controls to Count as two way as I suggested below.
Create a timer which fires off every second that checks two variables.
(Timer Check #1) Checks to see if a database request is ongoing (such as a Boolean flag). If it is true, it does nothing. If there is no operation (false), it goes to step 4.
(Timer Check #2) It checks to see if count is changed. If count has changed it sets the data request ongoing flag (as found/used in step 3) and initiates an async database call and exits.
(Database Action Call) Gets the database data and updates the VM accordingly. It sets the data request ongoing flag to false which allows the timer check to start a new request if count is changed.
That way you can manage the updates even if a user goes crazy with the slider.
I believe you may have over thought this. Remove all the events off of the slider and the textbox. If the first value (set programmatically) should not call your DoWhatever method, then put in a check in that code to skip the first initialization....
I recommend that you make the slider bind to Count as a TwoWay mode and have the Count Property do the other process you need (as shown on your entity class). No need to check for clicks or any other event. If the user changes the value in the textbox it changes the slider and visa versa.
<Slider Name="slider"
VerticalAlignment="Top"
Value="{Binding Count, Mode=TwoWay}"
Width="200"
Minimum="0"
Maximum="100" />
<TextBox VerticalAlignment="Top"
HorizontalAlignment="Left"
Grid.Column="1"
Width="100"
Text="{Binding Count,Mode=TwoWay,UpdateSourceTrigger=LostFocus}"
Height="25" />
I have read all the Q&A I could find here and on the MS forums about this exception, and tried most of the suggestions that I understood, and a few others. It seems that this exception can come up for a wide range of causes.
As with others, I have a WPF DataGrid bound to a collection, which throws this exception when one tries to edit one of the cells. They are set to be write-able, the collection is an ObservableCollection, I've implemented get and set handlers which send notification messages.
The suggestions I haven't tried are the ones involving implementing IList's non-generic interface, because I have no idea what I would do to do that. Also, I have many DataGrids bound to various lists and collections in my app which work, and this one used to work when it was bound to a LINQ collection.
Please help me figure out what I need to do here.
The Data Grid is:
<DataGrid Name="dgIngredients" Margin="567,32,0,44" Width="360" ItemsSource="{Binding}" IsReadOnly="False"
AutoGenerateColumns="False" HorizontalAlignment="Left" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Width="63" Header="Percent" Binding="{Binding Preference}" IsReadOnly="False" />
<DataGridTextColumn SortDirection="Descending" Width="301" Header="Ingredient" Binding="{Binding Ingredient}" IsReadOnly="True" CanUserSort="True" CanUserReorder="False" />
</DataGrid.Columns>
</DataGrid>
The column being edited is the non-read-only one, Preference.
The collection is:
private ObservableCollection<RAM_Ingredient> MemberIngredientPrefs = new ObservableCollection<RAM_Ingredient>();
The binding is:
dgIngredients.DataContext = MemberIngredientPrefs.OrderBy("Ingredient",true);
RAM_Ingredient is:
public class RAM_Ingredient : INotifyPropertyChanged
etc.
Where RAM_Ingredient.Preference is:
private int _Preference;
public int Preference
{
get
{
return _Preference;
}
set
{
// This is needed to send notification of changes (and to not throw an exception on grid edit!):
if ((_Preference != value))
{
SendPropertyChanging();
_Preference = value;
SendPropertyChanged("Preference");
}
}
}
The exception is:
System.InvalidOperationException was unhandled
Message='EditItem' is not allowed for this view.
Source=PresentationFramework
StackTrace:
at System.Windows.Controls.ItemCollection.System.ComponentModel.IEditableCollectionView.EditItem(Object item)
at System.Windows.Controls.DataGrid.EditRowItem(Object rowItem)
at System.Windows.Controls.DataGrid.OnExecutedBeginEdit(ExecutedRoutedEventArgs e)
etc...
I have also this problem, And found that the point here is that we can not edit a IEnumerable in a DataGrid, only a list can be edited.
therefore we didn't need to create a new class, its works also on a LINQ query with anonymous return type. it's need only to be a list.
here is a sample of my code:
dtgPrdcts.ItemsSource= ProductLists.Select(Function(x) New With {.ListTitle = x.ListTitle, .ProductID = x.ProductID, .License = "", .ForRemove = True}).ToList
I still don't know what specifically caused the problem, but I managed to work around it, and I'm not sure how much of what I did was overkill, but it works.
I created a new class just for the purpose of holding the data in the DataGrid rows. I make a List of objects of this class and fill it in and bind it to the DataGrid as I was doing before. I also added the usual stuff and nonsense for getting Change Notification to work (probably overkill) and I had to re-define a comparison function in a different way to get it to sort because of that whole comedy situation.
i.e.
List<UsablePref> MemberIngredientPrefs = new List<UsablePref>();
...
foreach (RAM_Ingredient ingredient in App.Ingredients)
{
ingredient.GetPreferences(EditorMember);
UsablePref pref = new UsablePref();
pref.Ingredient = ingredient.Ingredient;
pref.IngredientID = ingredient.IngredientID;
pref.Preference = ingredient.Preference;
MemberIngredientPrefs.Add(pref);
}
// Sort alphabetically by ingredient name,
MemberIngredientPrefs.Sort(UsablePref.CompareByName);
// and bind the ingredient prefs DataGrid to its corresponding List
dgIngredients.DataContext = MemberIngredientPrefs;
I had this same problem trying to create a list of rows from a join; since the LINQ query returns an IEnumerable, I had the DataGrid bound to that IEnumerable; this worked fine for readonly and oddly worked with ComboBoxes and some other custom controls I used, but plain text editing threw the InvalidOperationException. The solution was an ObservableCollection in place of the IEnumerable; basically from:
BoundView = (/*LINQ QUERY*/); // is IEnumerable<CustomJoinObject>
to
BoundView = new ObservableCollection<CustomJoinObject>(/*LINQ QUERY*/);
In both cases BoundView is the DataContext for the DataGrid.
I'm assuming this happens because IEnumerable doesn't have the machinery to support a datagrid, whereas ObservableCollection does.
The model class needs to implement the interface INotifyPropertyChanged coming from the namespace System.ComponentModel.
Class example:
public class Exemple : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#endregion INotifyPropertyChanged Members
}
even if this thread is old, it could help someone.
The issue is that LINQ returns a IEnumerable, as said VaelynPhi, but the cause is that editing a datagrid requires a source that implements IEditableCollectionView.
You can then use a list, an observableCollection or any collection implementing this interface.
I found this solution thanks to Shoe who gave an answer on this tread.
In my case this exception was thrown when I wanted to edit some cells. The problem was of wrong collection type bound to ItemSource => when I switched from IEnumerable<T> to ObservableCollection<T> everything works correctly.