WPF navigation via buttons - wpf

Question: Is there a way to make a button behave like a hyperlink inside of a user control?
I've been searching around for a few days now and haven't found anyone who has addressed this. How do you use a button to navigate in a WPF application? Specifically how do you make a button inside of a user control navigate it's host frame? Bare in mind that User controls do not have direct access to the host frame. so simply:
this.NavigationService.Navigate(new Uri(this.addressTextBox.Text));
won't work. I'm using user controls. If you are using only pages, this is the answer you are looking for, if you are using UserControls, look at my answer below.

I feel like a goof ball for answering my own question, but i figured it out at long last!
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
Dim pg As Page = CType(GetDependencyObjectFromVisualTree(Me, GetType(Page)), Page)
Dim newPage As %desired uri here% = New %desired uri here%
pg.NavigationService.Navigate(newPage)
End Sub
Private Function GetDependencyObjectFromVisualTree(ByVal startObject As DependencyObject, ByVal type As Type) As DependencyObject
'Walk the visual tree to get the parent(ItemsControl)
'of this control
Dim parent As DependencyObject = startObject
While (Not (parent) Is Nothing)
If type.IsInstanceOfType(parent) Then
Exit While
Else
parent = VisualTreeHelper.GetParent(parent)
End If
End While
Return parent
End Function
This function i found here (http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f8b02888-7e1f-42f4-83e5-448f2af3d207) will allow for the use of NavigationService inside of a user control.
~N

Use the NavigationService..::.Navigate Method:
void goButton_Click(object sender, RoutedEventArgs e)
{
this.NavigationService.Navigate(new Uri(this.addressTextBox.Text));
}

Related

Need to determine which grid a double-click came from?

I think this should be fairly simple, but I've been looking through the properties in the signature for the handler that I'm using and I don't see any way to suss out what I'm looking for.
I have a fairly simple WPF app with two DataGrid controls in the same window. I have a double click event defined in the XAML like so:
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<EventSetter
Event="MouseDoubleClick"
Handler="Row_DoubleClick"/>
</Style>
</DataGrid.ItemContainerStyle>
And in the code behind (do we call it that in WPF apps?) I have the Row_DoubleClick handler set up like so:
Private Sub Row_DoubleClick(ByVal sender As System.Object, ByVal e As System.Windows.Input.MouseButtonEventArgs)
Now the sub itself works fine and picks up the row that was double-clicked just fine. However, as I noted before I have two DataGrids that use this same sub for the double-click event. I realize one path might be to simply make two subs, but it seems like I should be able to use the one for both, and it's taking the exact same action in either case, just using the row from one DataGrid or the other.
It always defaults to the first, let's call it IncompleteGrid, if a row is selected even if the second DataGrid, let's call it CompleteGrid, is the the one being double clicked. I've been looking through the sender and e objects in debug mode, but I don't see any place or property I can check to see which grid the double-click is coming from.
Any ideas?
You can get the parent dataGrid from row by using VisualTreeHelper. Have this private method on your code (code is in C#, hope you can get it convert to VB easily):
private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
{
DataGridRow row = sender as DataGridRow;
DataGrid senderDataGrid = FindAncestor<DataGrid>(row);
}
private T FindAncestor<T>(DependencyObject dependencyObject)
where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null) return null;
var parentT = parent as T;
return parentT ?? FindAncestor<T>(parent);
}
VB Version:
Private Sub Row_DoubleClick(sender As Object, e As MouseButtonEventArgs)
Dim row As DataGridRow = TryCast(sender, DataGridRow)
Dim senderDataGrid As DataGrid = FindAncestor(Of DataGrid)(row)
End Sub
Private Function FindAncestor(Of T As DependencyObject)(dependencyObject As DependencyObject) As T
Dim parent = VisualTreeHelper.GetParent(dependencyObject)
If parent Is Nothing Then
Return Nothing
End If
Dim parentT = TryCast(parent, T)
Return If(parentT, FindAncestor(Of T)(parent))
End Function
This parameter should give you the information:
ByVal sender As System.Object
sender should be the grid that the double-click is coming from. (That's the meaning of sender -- the control that sent the event.)
You can cast sender to a DataGrid if you want to do specific stuff with it.
Edit: If sender is a DataGridRow instead of a DataGrid, then you could use this question to find the host DataGrid. (Using a RelativeSource or a CommandParameter seems to the accepted methods for this.)

navigate to link in xps document using wpf

I am trying to open an xps document in wpf with vb as a fixed document with documentviewer, then navigate to a bookmark/link within the document. I have unpacked the xps and found the available links in DocStucture.struct, but I don't know how to tell the documentviewer to go to the link's location. The documentviewer is contained within a Frame in a window and I can click on a link in the document's table of contents to the different links. The purpose is to allow the end user to open the document to a specific location when he/she pushes a button (the document is a user guide).
Can someone explain how to do this?
Thanks!
edit:
I have tried packing the link into a uri, however I can only figure out how to make the frame navigate to a uri not the documentviewer:
class for the window that contains the user manual:
Partial Public Class UserManual
Private Sub DocViewer_Loaded(ByVal sender as Object, ByVal e as System.Windows.RoutedEventArgs)
Dim documentName As String = "#.\User Manual.xps"
Dim xpsDoc As XpsDocument
xpsDoc = New XpsDocument(documentName, IO.FileAccess.Read)
DocViewer.Document = xpsDoc.GetFixedDocumentSequence
End Sub
Public Sub New()
MyBase.New()
Me.InitializeComponent()
End Sub
End Class
in the main window from which the user manual will be opened:
Private Sub Button_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs)
Dim UserManualWindow As UserManual = New UserManual
UserManualWindow.Show()
Dim uri = New Uri("pack://file:,,,/User Manual.xps#PG_8_LNK_94")
UserManualWindow.DocFrame.Navigate(uri)
End Sub
This doesn't work. The frame just shows the text of the uri. I can't find a similar method of the documentviewer. The gotopage method only takes in a page number, not a link.
So I managed to work through it and learned that I was close. Instead of commanding the frame to navigate to the uri, I just needed to set the frame's source:
UserManualWindow.DocFrame.Source = uri
Now the frame updates to the correct fragment within the xps document.

Apply and validate a bound DataGridViewComboBoxCell directly upon selection change

I have a windows forms DataGridView that contains some DataGridViewComboBoxCells that are bound to a source collection using DataSource, DisplayMember and ValueMember properties. Currently the the combobox cell commits the changes (i.e. DataGridView.CellValueChanged is raised) only after I click on another cell and the combobox cell loses focus.
How would I ideally commit the change directly after a new value was selected in the combobox.
This behaviour is written into the implementation of the DataGridViewComboBoxEditingControl. Thankfully, it can be overridden. First, you must create a subclass of the aforementioned editing control, overriding the OnSelectedIndexChanged method:
protected override void OnSelectedIndexChanged(EventArgs e) {
base.OnSelectedIndexChanged(e);
EditingControlValueChanged = true;
EditingControlDataGridView.NotifyCurrentCellDirty(true);
EditingControlDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
This will ensure that the DataGridView is properly notified of the change in item selection in the combo box when it takes place.
You then need to subclass DataGridViewComboBoxCell and override the EditType property to return the editing control subclass from above (e.g. return typeof(MyEditingControl);). This will ensure that the correct editing control is created when the cell goes into edit mode.
Finally, you can set the CellTemplate property of your DataGridViewComboBoxColumn to an instance of the cell subclass (e.g. myDataGridViewColumn.CellTemplate = new MyCell();). This will ensure that the correct type of cell is used for each row in the grid.
I tried using Bradley's suggestion, but it was sensitive to when you attached the cell template. It seemed like I couldn't allow the design view to wire up the column, I had to do it myself.
Instead, I used the binding source's PositionChanged event, and triggered updates from that. It's a little bit odd, because the control is still in edit mode, and the databound object doesn't get the selected value yet. I just updated the bound object myself.
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
(MyBoundType)bindingSource.Current.MyBoundProperty =
((MyChoiceType)comboBindingSource.Current).MyChoiceProperty;
}
A better way to achieve this that I am using successfully rather than subclassing or the somewhat inelegant binding source method above, is the following (sorry it's VB but if you can't translate from VB to C# you have bigger problems :)
Private _currentCombo As ComboBox
Private Sub grdMain_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles grdMain.EditingControlShowing
If TypeOf e.Control Is ComboBox Then
_currentCombo = CType(e.Control, ComboBox)
AddHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
End If
End Sub
Private Sub grdMain_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdMain.CellEndEdit
If Not _currentCombo Is Nothing Then
RemoveHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
_currentCombo = Nothing
End If
End Sub
Private Sub SelectionChangedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myCombo As ComboBox = CType(sender, ComboBox)
Dim newInd As Integer = myCombo.SelectedIndex
//do whatever you want with the new value
grdMain.NotifyCurrentCellDirty(True)
grdMain.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
That's it.

Handling events from user control containing a WPF textbox

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.
Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp
ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here
Seems basic, but did you set the KeyPreview to TRUE on your form?

Equivalent to a keypreview property in WPF

I'm pondering taking the plunge to WPF from WinForms for some of my apps, currently I'm working on the combined barcode-reader/text-entry program (healthcare patient forms).
To be able to process the barcode characters, I rely on the Keypreview property in WinForms (because barcodes can be scanned regardless of what control has the focus).
But I cannot seem to find a KeyPreview property in neither VS2008 or VS2010, for a WPF app.
Is there an alternative approach/solution to handle my barcode characters in WPF?
Rgrds Henry
use the override in your own UserControls or Controls (this is an override from UIElement)
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e) {
base.OnPreviewKeyDown(e);
}
if you want to preview the key down on any element which you dont create you can do this:
Label label = new Label();
label.PreviewKeyDown += new KeyEventHandler(label_PreviewKeyDown);
and then have a handler like so :-
void label_PreviewKeyDown(object sender, KeyEventArgs e) {
}
if you mark the event as handled (e.Handled = true;) this will stop the KeyDown event being raised.
Thanks got it working! Only problem was I'm coding in VB not C#, but the basic idea holds. Neat to create a label out of thin air and use it to insert yourself in the event stream.
If someone else is interested of the same solution but in VB for WPF, here's my test program, it manages to toss all 'a' characters typed, no matter what control has the focus:
Class MainWindow
Dim WithEvents labelFromThinAir As Label
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
AddHandler MainWindow.PreviewKeyDown, AddressOf labelFromThinAir_PreviewKeyDown
End Sub
Private Sub labelFromThinAir_PreviewKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
TextBox1.Text = e.Key ' watch 'em coming
If (44 = e.Key) Then e.Handled = True
End Sub
End Class
P.S. This was my first post on stackoverflow, really a useful site. Perhaps I'll be able to answer some questions in here myself later on :-)
WPF uses event bubbling and tunneling. In other words the events travel down and up the visual element tree. Some events will have a corresponding Preview event. So MouseDown will have a PreviewMouseDown that you can respond to. Check out this link and scroll down to the WPF Input Events section.

Resources