VB.NET WPF Data Binding On Control Property (How to update) - wpf

im trying to learn how to use WPF data binding.
I have a control, and i want to change the value f a property in the control.
<somecontrol Value="{Binding GoodRange}">
I created the property in the MainWindow Class as follows:
Public Property GoodRange As Double
Get
Return m_GoodRange
End Get
Set(value As Double)
m_GoodRange = value
End Set
End Property
Private m_GoodRange As Double
Inside the Mainwindow class i added the following to the sub New()
Public Sub New()
InitializeComponent()
GoodRange = 3000
Me.DataContext = Me
End Sub
So far so good, hwen i launch the program the value 3000 is passed to the control.
Now, during runtime i want to change the property for example when a user clicks on a button, or on a timed event eg:
Private Sub UpdateValue()
GoodRange = 2800
End Sub
When i do this, the value on the control is not updated. im trying to understand how i can trigger the control to update.
I have googled for 4 hours try try and understand, and i have found and tried a lot of answers on google, but usually these answers are for custom controls or custom classes or using the .datacontex method which i cant use as multiple property's will need to be changed.
I would be greatfull for any help you guys can offer.
Thank you/

As Clemens says in the comment to your question, you really need to do some research on MVVM, which has Data Binding as its heart and soul. An excellent article to start is of course the classic from Josh Smith, MVVM Design Pattern
In the meantime, as a minimum functional example, you should create a class as shown below that implements the INotifyPropertyChanged interface (code below is in C#):
public class myViewModel : INotifyPropertyChanged
{
private double goodRange = 3000;
public double GoodRange
{
get
{
return goodRange;
}
set
{
if (value != goodRange)
{
goodRange = value;
NotifyPropertyChanged("GoodRange");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
In the code behind of your window:
Dim mVM as myViewModel
Public Sub New()
InitializeComponent()
mVM = new myViewModel()
Me.DataContext = mVM
End Sub
Private Sub UpdateValue()
mVM.GoodRange = 2800
End Sub

Related

WPF Can I use a DataTrigger to have the View do something after the View Model changes the value of a Property?

My WPF MVVM VB.NET app loads a list of songs into a ListBox at start. The list contents populate in a BackgroundWorker that is kicked off in the Constructor of the ViewModel. Once this is done, I want to set focus to the first song in the list.
As setting this focus is purely a View operation, I want it in the code-behind of the XAML. It's no business of the ViewModel where focus goes.
I tried doing this on various Window and ListBox events, but they either don't fire, or fire too early. So I'm think what I need is a Boolean Property that the ViewModel sets when it's done loading the songs into the list. That's when I need the View to catch that Property Change, and call the code-behind function that has the logic to maniuplate the View, in the is case, setting focus on the first song in the list.
But this is where my knowledge of WPF is short. I searched and it sounds like DataTrigger could do the trick. But where to put it, and what's the right syntax, and how to have it call my code-behind function?
Or is there an even simpler way that I'm overlooking. This seems like a basic functionality - to trigger some code-behind action in the View when a Property changes a certain way in the ViewModel.
Here's the code-behind function. I can elaborate it once it's successfully getting called at the intended time:
Private Sub FocusSongsList()
' set focus back to the Songs list, selected item (couldn't just set focus to the list, it ran forever and looks like it set focus to every item in turn before releasing the UI)
Dim listBoxItem = CType(LstSongs.ItemContainerGenerator.ContainerFromItem(LstSongs.SelectedItem), ListBoxItem)
If Not listBoxItem Is Nothing Then
listBoxItem.Focus()
End If
End Sub
Here's my ListBox:
<ListBox x:Name="LstSongs" ItemsSource="{Binding FilteredSongs}" DisplayMemberPath="Path"
HorizontalAlignment="Stretch"
SelectionMode="Extended" SelectionChanged="LstSongs_SelectionChanged" Loaded="FocusSongsList"/>
And I would define a new property that can be set from the RunWorkerCompleted part of the BackgroundWorker.
Private _InitialSongLoadCompleted As Boolean
Public Property InitialSongLoadCompleted() As Boolean
Get
Return _InitialSongLoadCompleted
End Get
Set(ByVal value As Boolean)
_InitialSongLoadCompleted = value
RaisePropertyChanged("InitialSongLoadCompleted")
End Set
End Property
A DataTrigger can't execute methods. It can only set properties.
Focus can't be activated by setting a property, therefore a DataTrigger can't solve your problem.
Generally, if you have longrunning initialization routines you should move them to an init routine (which could be async) or use Lazy<T>.
For example, you instantiate your view model class and call Initialize() afterwards. After the method has returned you can continue to initialize the ListBox:
MainWindow.xaml.cs
Partial Class MainWindow
Inherits Window
Private ReadOnly Property MainViewModel As MainViewModel
Public Sub New(ByVal dataContext As TestViewModel, ByVal navigator As INavigator)
InitializeComponent()
Me.MinViewModel = New MainViewMdel()
Me.DataContext = Me.MainViewModel
AddHandler Me.Loaded, AddressOf OnLoaded
End Sub
' Use the Loaded event to call async methods outside the constructor
Private Async Sub OnLoaded(ByVal sender As Object, ByVal e As EventArgs)
Await mainViewModel.InitializeAsync()
' For example handle initial focus
InitializeListBox()
End Sub
End Class
MainViewModel.cs
Class MainViewModel
Inherits INotifyPropertyChanged
Public Async Function InitializeAsync() As Task
Await Task.Run(AddressOf InitializeSongList)
End Function
Private Sub InitializeSongList()
' TODO::Initialize song list
End Sub
End Class
It has been a long long time since I wrote much VB, so I'm afraid this is c# code.
You can handle targetupdated on a binding.
This fires when data transfers from the source ( viewmodel property ) to the target (the ui property and here itemssource)
<ListBox
x:Name="LstSongs"
ItemsSource="{Binding Songs, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
When you replace your list, that targetupdated will fire.
If you raise property changed then the data will transfer ( obviously ).
private async void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
await Task.Delay(200);
var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
.ContainerFromItem(LstSongs.Items[0]);
firstItem.Focus();
Keyboard.Focus(firstItem);
}
As that data transfers, there will initially be no items at all of course so we need a bit of a delay. Hence that Task.Delay which will wait 200ms and should let the UI render. You could make that a bit longer or dispatcher.invokeasync.
It finds the first container and sets focus plus keyboard focus.
It might not be at all obvious that item has focus.
A more elegant approach using dispatcher will effectively schedule this focussing until after the ui has rendered. It might, however, look rather tricky to someone unfamiliar with c#
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeAsync(new Action(() =>
{
var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
.ContainerFromItem(LstSongs.Items[0]);
firstItem.Focus();
Keyboard.Focus(firstItem);
}), DispatcherPriority.ContextIdle);
}
If you want a blue background then you could select the item.
firstItem.IsSelected = true;
Or you could use some datatrigger and styling working with IsFocused.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.isfocused?view=windowsdesktop-7.0
( Always distracts me that, one s rather than two and IsFocussed. That'll be US english I gues )
Here's my mainwindowviewmodel
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private List<string> songs = new List<string>();
MainWindowViewModel()
{
Task.Run(() => { SetupSongs(); });
}
private async Task SetupSongs()
{
await Task.Delay(1000);
Songs = new List<string> { "AAA", "BBB", "CCC" };
}
}
I'm using the comunity toolkit mvvm for code generation. Maybe it does vb as well as c#.
You might accomplish your goal by defining a custom event in the viewmodel which is raised when the list processing is complete. The view can subscribe to it and act accordingly.
It would look something like this:
Class MyViewModel
'Custom eventargs shown for completeness, you can use EventHandler if you
'don't need any custom eventargs.
Public Event ListCompleted As EventHandler(Of ListCompletedEventArgs)
'...
Public Sub ProcessSongList()
'Note that if this runs on a background thread, you may need to
'get back on the UI thread to raise an event for the view to handle
RaiseEvent ListCompleted(Me, New ListCompletedEventArgs())
End Sub
End Class
Class MyView
Public Sub New(ByVal vm as MyViewModel)
Me.DataContext = vm
AddHandler vm.ListCompleted, AddressOf OnListCompleted
End Sub
Private Sub OnListCompleted(ByVal sender As Object, ByVal args As ListCompletedEventArgs)
'...
End Sub
'...
End Class
You mentioned doing processing on a background thread. I'm not completely sure which thread the completion event would issue into, but beware that UI stuff can only happen on the UI thread so you might need to use a Dispatcher.Invoke to make sure your code runs on the right thread. I'd do it to run the RaiseEvent so the view doesn't need to know anything about it.

Updating label binding with property isn't setting right the first time

I have a simple application, and i'm trying to get a grasp on binding, and properties.
What I'm trying to do:
Load a modal window, from the main window. I know this blocks all access to it, so I'm trying to update a label on the modal window through a property binding.
I set the datacontext:
DataContext = busyupdate
InitializeComponent()
Set the variable:
Property busyupdate As BusyWindowGetSet = New BusyWindowGetSet
Then I bind the label to the current context:
Content="{Binding Stage, UpdateSourceTrigger=PropertyChanged}"
Content="{Binding Description, UpdateSourceTrigger=PropertyChanged}"
Here is my BusyWindowGetSet class, where I have 2 properties... Stage and Description. It implements INotifyPropertyChanged
Imports System.ComponentModel
Public Class BusyWindowGetSet
Implements INotifyPropertyChanged
Public Property Stage() As String
Get
Return m_stage
End Get
Set(value As String)
m_stage = value
NotifyPropertyChanged("Stage")
End Set
End Property
Private Shared m_Stage As String
Public Property Description() As String
Get
Return m_description
End Get
Set(value As String)
m_description = value
NotifyPropertyChanged("Description")
End Set
End Property
Private Shared m_Description As String
#Region "INotifyPropertyChanged Members"
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
#End Region
#Region "Private Helpers"
Public Sub NotifyPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
#End Region
End Class
When I load the "busy window" the labels are not updated.. but If I load the window a second time, they show up and change like normal.
This is how I'm setting the properties...
busyupdate.Stage = String.Format("Stage: {0}", "Warming up1")
busyupdate.Description = String.Format("Description: {0}", "Warming up1")
What am I missing here?
Update for Mark:
Dim mm As New BusyWindow
mm.ShowDialog()
busyupdate.Stage = String.Format("Stage: {0}", "Warming up1")
busyupdate.Description = String.Format("Description: {0}", "Warming up1")

VB.net WPF DataGrid ObservableCollection Binding property update

I am using VB.NET and WPF within Visual Studio 2010 Express.
Currently, I have:
A DataGrid by the name of downloadListDG. This has a column which is a template containing an image.
An ObservableCollection of a custom DownloadListItem class.
This DownloadListItem has a public property which is another custom class.
This class has a private dim which is a StateType (a custom enum), and a public readonly property which returns a string depending on what the StateType is (actually an image URI if you're curious).
The DownloadListItem also has a public property which just returns the StateType (this is just for binding purposes)
My problem is that whenever the StateType changes, the image column in the DataGrid does not change. I have been trying to use the IPropertyChangedNofity, but nothing changes, so either I'm using it incorrectly or I need to use another method.
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
AddHandler ControllerRef.StateChanged, AddressOf StateChangeHandler
Private Sub StateChangeHandler(NewState As State)
MsgBox(NewState)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("CurrentState"))
End Sub
Thanks in advance
Make sure the PropertyChanged event is notifying the UI of the property name you are bound to, not the property that triggers the change. Example:
Imports System.ComponentModel
Public Class DownloadListItem : Implements INotifyPropertyChanged
Friend Enum StateEnum
State1 = 0
State2 = 1
End Enum
Private _CurrentState As StateEnum
Private Sub ChangeEnumValue(NewValue As StateEnum)
_CurrentState = NewValue
OnPropertyChanged("ImageURI")
End Sub
Public ReadOnly Property ImageURI As String
Get
' TODO: Implement conditional logic to return proper value based on CurrentState Enum
End Get
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
End Class

How do I fire a custom event from a User Control?

I have a very interesting task that I need help with. The description is as follows:
I have a user control (SomeUserControl), that I am using in a Main Window. I write my application entirely on SomeUserControl, and Main Window is hosting SomeUserControl and nothing else.
Right now I have a shutdown button (in SomeUserControl), that is supposed to close the Main Window. The reason for this is that I do not want anything from SomeUserControl to close the application itself, but to fire an event from SomeUserControl, and Main Window receives it, and Main Window will close the application instead of SomeUserControl.
How do I do it? I am not familiar with the concept of creating and handling custom events, so if someone could explain it in words and in code as an example, I will be very grateful to you!
Edit: Here's my code so far.
(in Window 2)
Public Event CloseApp As EventHandler
Private Sub CancelButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles CancelButton.Click
DialogResult = False
RaiseEvent CloseApp(Me, New EventArgs)
End Sub
(In Main Window)
Public loginPage As New LoginPage
Public Sub New()
InitializeComponent()
AddHandler loginPage.CloseApp, AddressOf Me.ShutDownJobChangeWindow
End Sub
Private Sub ShutDownJobChangeWindow(ByVal sender As Object, ByVal e As EventArgs)
Application.Current.Shutdown()
End Sub
Goal: I want to close the application when I click cancel in Window 2, but I don't want to do it in such a way that Window 2 closes itself, but by sending some notification to Main Window, and Main Window closes the application.
If the logic of the user control is implemented in the "code behind" of the user control class, do the following.
I'm assuming the XAML file has somewhere a button with a click event:
<Button Click="Button_Click">Close App</Button>
Then, in your code behind for SomeUserControl class, do the following:
public partial class SomeUserControl : UserControl
{
public event EventHandler CloseApp;
...
private void Button_Click( object sender, RoutedEventArgs e )
{
if( CloseApp != null ) {
CloseApp( this, new EventArgs( ) );
}
}
}
In your Main window, listen to the event:
...
someUserCtrl.CloseApp += new EventHandler( MyFn );
private void MyFn( object sender, object EventArgs e ) {
...
}
The simplest way to do this is to declare a custom event:
public delegate void ShutdownEventHandler (object sender, EventArgs data);
public event ShutdownEventHandler Shutdown;
protected void OnShutdown(EventArgs args)
{
if(Shutdown != null)
Shutdown(this, args);
}
Then you subscribe to the Shutdown event in MainWindow, and handle shutdown logic there.
And in SomeUserControl, you simply run OnShutdown when you want to shutdown your application.
I had the same problem, and I've solved in this way:
if you are using a soft version of MVVM (with soft I mean that you use codebehind for events handling) and your event is within the ModelView class do this:
In your MainWindow:
public MainWindow(ViewModels.MyViewModel vm)
{
InitializeComponent();
//pass the istance of your ViewModel to the MainWindow (for MVVM patter)
this.vm = vm;
//Now pass it to your User Control
myUserControl.vm = vm;
}
In your UserControl
public partial class MyUserControl: UserControl
{
public ViewModels.MyViewModel vm;
public MyUserControl()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
vm.MyMethod();
}
}
Last but not least, in your MainWindow's xaml insert your user control and give it a name:
<local:MyUserControl x:Name="myUserControl" />
If you don't want to use a ViewModel, you can simply pass to your UserControl an instance of your MainWindow and then you can run within the codebehind of your usercontrol all of your MainWindow's public methods.
Hope it will help!
Sorry, the question is VB so here is the VB version of the code above:
MainWindow:
Public Sub New(ByVal vm As ViewModels.MyViewModel)
InitializeComponent()
Me.vm = vm
myUserControl.vm = vm
End Sub
UserControl:
Public Partial Class MyUserControl
Inherits UserControl
Public vm As ViewModels.MyViewModel
Public Sub New()
InitializeComponent()
End Sub
Private Sub button_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
vm.MyMethod()
End Sub
End Class

Create A WPF ObservableCollection From A SubSonic 2.2 Collection

If I have a DAL created by SubSonic 2.2, how do I convert the Collections created by it to WPF ObservableCollections in code (pref.VB.NET) to be consumed by WPF?
Sorry !VB:
[Test]
public void Exec_Testing()
{
ProductCollection products =
DB.Select().From("Products")
.Where("categoryID").IsEqualTo(5)
.And("productid").IsGreaterThan(50)
.ExecuteAsCollection<ProductCollection>();
Assert.IsTrue(products.Count == 77);
ObservableCollection<Product> obsProducts = new ObservableCollection<Product>(products);
}
You would have to manually add this to your DAL classes, but it's not too hard. In the top of each data access layer class, add "Implements INotifyPropertyChanged" and then in each property, add the code in the "set" like you see below.
Private _Book As String
Public Property Book() As String
Get
Return _Book
End Get
Set(ByVal value As String)
If Not _Book = value Then
_Book = value
' Raise the property changed event.
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Book"))
End If
End Set
End Property
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

Resources