Attached property not updating my view model - wpf

I used the technique presented in this SO post (Ray Burn's answer) to bind my RichTextBox to the underlying string-type property of my View Model (VM). The problem I'm facing is that the underlying field doesn't appear to be updating when user types something in the RichTextBox, even after focus changes. Here's the relevant XAML (SyntaxHighlighterTextBox is a custom control that inherits from RichTextBox):
<local:SyntaxHighlighterTextBox
local:RichTextBoxHelper.DocumentXaml="{Binding Query, UpdateSourceTrigger=PropertyChanged}" />
Here is the Query property of the underlying VM:
Public Property Query() As String
Get
Return mQuery
End Get
Set(ByVal value As String)
mQuery = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Query"))
End Set
End Property
Edit
Here's my handler's code:
Public Shared Sub MyHandler(obj As DependencyObject, e As DependencyPropertyChangedEventArgs)
Dim rtb = DirectCast(obj, SyntaxHighlighterTextBox)
Dim xaml = GetDocumentXaml(rtb)
Dim doc = New FlowDocument()
range = New TextRange(doc.ContentStart, doc.ContentEnd)
range.Load(New MemoryStream(Encoding.UTF8.GetBytes(xaml)), DataFormats.Text)
rtb.Document = doc
AddHandler range.Changed, Sub(obj2, e2)
If rtb.Document Is doc Then
Dim buffer As New MemoryStream()
range.Save(buffer, DataFormats.Text)
SetDocumentXaml(rtb, Encoding.UTF8.GetString(buffer.ToArray()))
End If
End Sub
End Sub

Related

What is the best way to update the source of a XamDataGrid from a different form?

I have a XamDataGrid in my MainWindow which has a Public Shared List(Of Artikelstammdaten) as DataSource. After opening a few other forms I want to add more data to the XamDataGrid with a button click. I thought the easiest way would be just to update the DataSource, but I get an Error:
The reference to an unreleased member requires an object reference.
This is what I have tried:
Private Sub Add_Click(sender As Object, e As RoutedEventArgs)
Dim update = MainWindow.listArtikelstammdaten.Concat(CType(Import.ComparedAccessData, IEnumerable(Of Artikelstammdaten)))
dgArticleMasterData.DataSource = update
Me.Close()
End Sub
If dgArticleMasterData is defined in the MainWindow class, you need to get a reference to the MainWindow instance to be able to access it.
You should be able to find it in the Application.Current.Windows collection:
Private Sub Add_Click(sender As Object, e As RoutedEventArgs)
Dim update = MainWindow.listArtikelstammdaten.Concat(CType(Import.ComparedAccessData, IEnumerable(Of Artikelstammdaten)))
Dim window = Application.Current.Windows.OfType(Of MainWindow).FirstOrDefault()
If window IsNot Nothing Then
window.dgArticleMasterData.DataSource = update
End If
Me.Close()
End Sub

ListView with grouping and sorting not refresh while INotifyPropertyChanged used

I have my own class which uses INotifyPropertyChanged correctly (Raising updates events), but when a property of type DateTime updated and called (View.Run) the listView not updating untill another property changing (not this property)
Now with the code:
Public Class EntryInfo
Implements INotifyPropertyChanged
ReadOnly Property DateAccessed As Date
Get
.......
Return _Access
End Get
End Property
Readonly Property Property1 as object
Get
.......
Return _Property1
End Get
End Property
Friend Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
RaiseEvent ApropertyHasChanged()
End Sub
Then when I need to Change the "DateAccessProperty" I use this code:
Friend Sub SetAccessTime(Dt As Date)
_Access = Dt
NotifyPropertyChanged("DateAccessed")
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''
After this I have a ListView named "LV1"
Dim Coll as new observableCollection(Of EntryInfo)
....... filing "Coll" with items (EntryInfo)
Lv1.ItemsSource =Coll
Then I do the following:
Do some sort and group operations.
Changing "DateAccessed" value. so that the "ApropertyHasChanged" event fired and at this point I used the following code
Private Sub RefreshViewNow()
Dim _view As ListCollectionView = TryCast(CollectionViewSource.GetDefaultView(LV1.ItemsSource), ListCollectionView)
If _view IsNot Nothing Then _view.Refresh()
'\\\ Items.Refresh()
End Sub
But _view not refreshed.
But if the property "Property1" changed the _View refreshed.
Any help?
The solution is by set the following "_view" properties:
_view.IsLiveFiltering = True
_view.IsLiveGrouping = True
_view.IsLiveSorting = True
or at least one of them if you want one of them only to be activated.

Setting up comboboxes in a datagridview bound to a collection

I have a question that I hope someone could help me with. My question involves winforms and the datagridview in Visual Studio 2008. I want to bind my datagridview to a collection of business objects instead of a dataset. I would like to have comboboxes in the datagridview which get its value from one of the properties in the business object. Is this possible? Can someone please provide example code or provide a web page which describes how to do this. Any help would be appreciated.
Thanks,
Greg
Here is a simple data class:
Public Class MyData
Private _ID As Integer
Private _ItemValue As String
Public Sub New(ByVal id As Integer, ByVal itemValue As String)
_ID = id
_ItemValue = itemValue
End Sub
ReadOnly Property ID() As Integer
Get
Return _ID
End Get
End Property
Public Property ItemValue() As String
Get
Return _ItemValue
End Get
Set(ByVal value As String)
_ItemValue = value
End Set
End Property
End Class
Create a form and place a DataGridView control on it, add this code:
Private myList As New List(Of String)
Private myItems As New List(Of MyData)
Protected Overrides Sub OnLoad(ByVal e As EventArgs)
MyBase.OnLoad(e)
myList.Add("First Item")
myList.Add("Last Item")
myItems.Add(New MyData(1, "Last Item"))
myItems.Add(New MyData(2, "First Item"))
DataGridView1.AutoGenerateColumns = False
DataGridView1.Columns.Add(New DataGridViewTextBoxColumn() With _
{.HeaderText = "ID", _
.DataPropertyName = "ID"})
DataGridView1.Columns.Add(New DataGridViewComboBoxColumn() With _
{.HeaderText = "ItemValue", _
.DataSource = myList, _
.DataPropertyName = "ItemValue"})
DataGridView1.DataSource = myItems
End Sub
Since you want comboboxes in your grid, you have to set AutoGenerateColumns to false and create them yourself, mapping each column to the property in your class through the DataPropertyName property. For the combobox, you can set its own DataSource for the list of drop down items.

Periodically update silverlight view with MVVM

I am trying to use MVVM in Silverlight, but I am quite new to it so I am not quite sure on some things. I have a silverlight page which displays the progress of some server side operation. The current progress comes from a web service and should be refreshed every few seconds (lets say 10 seconds for the sake of the argument).
What is the best way to implement this? The options I could think of was:
Initalize a DispatcherTimer in the Initalize method of my ViewModel and refresh the view from the DispatcherTimer event (putting the timer details in the ViewModel)
Create a wrapper arround DispatcherTimer (e.g. PeriodicCommandExecutor) which would be a Control or resource similar to the Timer control in WindowsForms with a command property that I bind to a Refresh command in the ViewModel (putting the timer details in the View)
I think the second option is preferred, because it makes the ViewModel easier to test and DispatcherTimer is an UI implementation detail which I don't want in my ViewModel propably. Do you agree?
If yes, how would you create such a wrapper. I started doing an DependencyObject with attached properties, but I am not sure how to forward the property values like Interval to the internal DispatcherTimer. Silverlight doesn't seem to provide any events when the dependency properties change and DispatcherTimer is not a DependencyObject so I can't databind directly to its properties.
Thanks!
Why use a DispatcherTimer? Why not use an ordinary System.Threading.Timer, which will fire its callback on a background thread?
If you put your UI progress update somewhere inconspicious (i.e. not in the centre of the UI, maybe in a bottom corner or status bar), then have the background timer chugging away while the user carries on with what they were doing. The progress value can be populated into the viewmodel, and shown on the UI using binding. This way you don't have to tie up the UI thread making web service calls.
At the end I solved my dillema creating a behavior which periodically executes a refresh command on the ViewModel which you can specify.
The code for the behavior is like this
(sorry for VB code):
Option Strict On
Imports System.Windows.Threading
Imports System.Windows.Interactivity
Namespace View.Behaviors
Public Class RefreshBehavior
Inherits Behavior(Of FrameworkElement)
Public Property Command As ICommand
Get
Return DirectCast(GetValue(CommandProperty), ICommand)
End Get
Set(ByVal value As ICommand)
SetValue(CommandProperty, value)
End Set
End Property
Public Shared ReadOnly CommandProperty As DependencyProperty = _
DependencyProperty.Register("Command", _
GetType(ICommand), GetType(RefreshBehavior), _
New PropertyMetadata(Nothing))
Public Property CommandParameter As Object
Get
Return GetValue(CommandParameterProperty)
End Get
Set(ByVal value As Object)
SetValue(CommandParameterProperty, value)
End Set
End Property
Public Shared ReadOnly CommandParameterProperty As DependencyProperty = _
DependencyProperty.Register("CommandParameter", _
GetType(Object), GetType(RefreshBehavior), _
New PropertyMetadata(Nothing))
Public Property Interval As TimeSpan
Get
Return DirectCast(GetValue(IntervalProperty), TimeSpan)
End Get
Set(ByVal value As TimeSpan)
SetValue(IntervalProperty, value)
End Set
End Property
Public Shared ReadOnly IntervalProperty As DependencyProperty = _
DependencyProperty.Register("Interval", _
GetType(TimeSpan), GetType(RefreshBehavior), _
New PropertyMetadata(TimeSpan.Zero, AddressOf OnIntervalUpdate))
Public Property Enabled As Boolean
Get
Return DirectCast(GetValue(EnabledProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(EnabledProperty, value)
End Set
End Property
Public Shared ReadOnly EnabledProperty As DependencyProperty = _
DependencyProperty.Register("Enabled", _
GetType(Boolean), GetType(RefreshBehavior), _
New PropertyMetadata(False, AddressOf OnEnabledUpdate))
Dim WithEvents timer As New DispatcherTimer()
Private Shared Sub OnEnabledUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim enable As Boolean = CType(e.NewValue, Boolean)
Dim executor As RefreshBehavior = CType(d, RefreshBehavior)
If Not executor.attached Then Return
Dim timer As DispatcherTimer = executor.timer
If enable AndAlso Not timer.IsEnabled Then
timer.Start()
ElseIf Not enable AndAlso Not timer.IsEnabled Then
timer.Stop()
End If
End Sub
Private Shared Sub OnIntervalUpdate(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
Dim executor As RefreshBehavior = CType(d, RefreshBehavior)
Dim timer As DispatcherTimer = executor.timer
timer.Interval = CType(e.NewValue, TimeSpan)
End Sub
Private WithEvents attachedObject As FrameworkElement
Private Sub OnUnload(ByVal sender As Object, ByVal e As EventArgs) Handles attachedObject.Unloaded
timer.Stop()
End Sub
Private attached As Boolean = False
Protected Overrides Sub OnAttached()
attached = True
attachedObject = AssociatedObject
If Enabled Then timer.Start()
MyBase.OnAttached()
End Sub
Protected Overrides Sub OnDetaching()
timer.Stop()
attached = False
attachedObject = Nothing
MyBase.OnDetaching()
End Sub
Private Sub OnTick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer.Tick
Dim cmd = Command
Dim parameter = CommandParameter
If Interval < TimeSpan.MaxValue AndAlso cmd IsNot Nothing AndAlso cmd.CanExecute(parameter) Then
cmd.Execute(parameter)
End If
End Sub
End Class
End Namespace
You can use it like this:
<i:Interaction.Behaviors>
<Behaviors:RefreshBehavior Enabled="True" Interval="0:0:10" Command="{Binding RefreshPageCommand}" />
</i:Interaction.Behaviors>
I hope it helps someone with a similar problem.

WPF distinguish between coding-SelectionChanged and the mouse-SelectionChanged

I have a machiavellian question (for me).
In my WPF application I have a ListBox that has in the ItemTemplate a Combobox. When the user select a ComboBoxItem, I have to do some complex operations on the ObservableCollection that is the ItemsSource of the ListBox, then I have to show the ListBox with the changed data. The problem is that if I handle the event "SelectionChanged" of the ComboBox control, every time I modify the source-class of the comboboxItems I enter in the method that handle the event, and this generate wrong results. In short I have to distinguish, in some way, between the SelectionChanged generated by code, and the SelectionChanged generated manually by the user with the mouse.
I have tried many ways, but nothing that works :-(
The soution I thought was the best, is to handle the event "GotFocus" or "MouseUp" of the ContentPresenter of the ItemContainerStyle of the Combo, or else to handle the same events ("GotFocus" and "MouseUp") of the ItemsPanel of the Combo, but the method I handled didn't capture the event (in debug the cursor doesn't enter at all in the method).
I can't use a boolean to stop the method "SelectionChanged" until the "first round" is finished, because the changes of the source-class of the ComboBoxItems occurs after that the method has been all executed.
The default value of the Combos is not always the first (it would be too easy :-)), and not always the same. Everytime the user select an item of one of the Combo, the default value of the other Combos has to change.
Can you help me?
Pileggi
' XAML
<Style x:Key="modComboCriteriEventParts" TargetType="{x:Type ComboBox}">
<EventSetter Event="Selector.SelectionChanged" Handler="cb_SelectionChanged"/>
</Style>
<DataTemplate x:Key="modLBoxCriteriParts">
<ComboBox Style = "{StaticResource modComboCriteriEventParts}"
ItemsSource = "{Binding CriteriItemList}"
ItemContainerStyle = "{DynamicResource modComboContainerParts}"
SelectedIndex = "{Binding valueSelected}" ... />
</DataTemplate>
<ListBox x:Name="lbCriteri" IsSynchronizedWithCurrentItem="True"
ItemsSource = "{Binding CriteriList, Source={StaticResource P_CriteriDataSource}}"
ItemTemplate = "{DynamicResource modLBoxCriteriParts}"
... />
' Code Behind
Private Sub cb_SelectionChanged(ByVal sender As System.Object, ByVal e As SelectionChangedEventArgs)
Dim ri as New RicambiCriteriList() As ObservableCollection(Of P_CriteriItem)
' some complex operations with ri ...
be = BindingOperations.GetBindingExpression(Me.lbCriteri, ListBox.ItemsSourceProperty)
Dim allCriteri As P_Criteri = DirectCast(be.DataItem, P_Criteri)
allCriteri.AddData (ri)
e.Handled = True
End Sub
' Source-Class
Public Class P_Criteri
Private _CriteriList As New ObservableCollection(Of P_CriteriItem)
Public ReadOnly Property CriteriList() As ObservableCollection(Of P_CriteriItem)
Get
CriteriList = _CriteriList
End Get
End Property
Public Sub AddData(ByVal CriteriListPass As ObservableCollection(Of P_CriteriItem))
_CriteriList.Clear()
For Each a As P_CriteriItem In CriteriListPass
_CriteriList.Add(a)
Next
End Sub
End Class
Public Class P_CriteriItem
Implements INotifyPropertyChanged
Public Sub New(ByVal criterioPass As String, ByVal CriteriItemListPass As ObservableCollection(Of P_CriteriItemValore), _
ByVal widthCriteriValuesPass As Double)
Me._criterio = criterioPass
Me._CriteriItemList = CriteriItemListPass
Me._widthCriteriValues = widthCriteriValuesPass
End Sub
Private _criterio As String = ""
Private _CriteriItemList As New ObservableCollection(Of P_CriteriItemValore)
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property criterio() As String
Get
Return Me._criterio
End Get
Set(ByVal value As String)
If Not Object.Equals(Me._criterio, value) Then
Me._criterio = value
Me.OnPropertyChanged ("criterio")
End If
End Set
End Property
Public Property CriteriItemList() As ObservableCollection(Of P_CriteriItemValore)
Get
Return Me._CriteriItemList
End Get
Set(ByVal value As ObservableCollection(Of P_CriteriItemValore))
If Not Object.Equals(Me._CriteriItemList, value) Then
Me._CriteriItemList = value
Me.OnPropertyChanged ("CriteriItemList")
End If
End Set
End Property
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
If handler IsNot Nothing Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
Public Class P_CriteriItemValore
Implements INotifyPropertyChanged
Public Sub New(ByVal criterioValorePass As String)
Me._criterioValore = criterioValorePass
End Sub
Private _criterioValore As String = Nothing
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public Property criterioValore() As String
Get
Return Me._criterioValore
End Get
Set(ByVal value As String)
If Not Object.Equals(Me._criterioValore, value) Then
Me._criterioValore = value
Me.OnPropertyChanged ("criterioValore")
End If
End Set
End Property
Protected Overridable Sub OnPropertyChanged(ByVal propertyName As String)
Dim handler As PropertyChangedEventHandler = Me.PropertyChangedEvent
If handler IsNot Nothing Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End If
End Sub
End Class
Firstly I think its better to handle events on the item container itself and not on the content presenter within the item. And now that I think of it that's probably why you don't see the events. The container is probably eating the events for selection.
But either way if you can't catch the MouseDown/GotFocus events, you can use the PreviewMouseDown/PreviewGotFocus events. Just in case you are not sure what these mean you should read up on wpf event routing architecture and bubbling and tunneling events.

Resources