WPF: Avoid Canvas scrollviewer reset - scrollviewer

I've the following problem: when an item in the canvas is selected (to be erased), the scrollviewer is always reset to 0: this is due to the focus in the example code. If the focus() is removed, the scrollviewer is Ok, but the selected item now can't be erased!>
Mainwindow.Xaml code:
<Border Grid.Row="1" BorderThickness="1" BorderBrush="Navy" Margin="2" Padding="2" >
<ScrollViewer Name="Posizione_scrollbar" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
Protected Overrides Sub OnPreviewMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)
MyBase.OnPreviewMouseDown(e)
' usual selection business
Dim designer As DesignerCanvas = TryCast(VisualTreeHelper.GetParent(Me), DesignerCanvas)
If designer IsNot Nothing Then
If (Keyboard.Modifiers And (ModifierKeys.Shift Or ModifierKeys.Control)) <> ModifierKeys.None Then
If Me.IsSelected Then
designer.SelectionService.RemoveFromSelection(Me)
Else
designer.SelectionService.AddToSelection(Me)
End If
ElseIf Not Me.IsSelected Then
If MainViewModel.Instance.ActiveDiagram.STMonitor = False Then
designer.SelectionService.SelectItem(Me)
End If
End If
'Here is the problem: the canvas scrollbar is resetted to 0!
Me.Focus()
End If
'True per avere la gestione col tasto sinistro del mouse
e.Handled = True
End Sub

You should try to handle RequestBringIntoView event raised when the item receives focus and prevent this event to be bubbled to ScrollViewer. One good place to mark these events as handled is at Canvas level.

Related

How to give a "ctrl + " shortcut key for a button in vb.net

how to add a " ctrl + " shortcut to button in vb.net. For example I need to perform click event of save button when ctrl + s is pressed.
Winforms Solution
In your Form class, set its KeyPreview property to true, example of it being set in the Form constructor, either set it here or via the Designer:
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Me.KeyPreview = True
End Sub
Then all you need to do is handle the KeyDown event for the Form, like this:
Private Sub Form1_KeyDown(sender As Object, e As KeyEventArgs) Handles MyBase.KeyDown
If (e.Control AndAlso e.KeyCode = Keys.S) Then
Debug.Print("Call Save action here")
End If
End Sub
WPF Solution (Not using MVVM pattern)
Add this to your .xaml file
<Window.Resources>
<RoutedUICommand x:Key="SaveCommand" Text="Save" />
</Window.Resources>
<Window.CommandBindings>
<CommandBinding Command="{StaticResource SaveCommand}" Executed="SaveAction" />
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="S" Modifiers="Ctrl" Command="{StaticResource SaveCommand}" />
</Window.InputBindings>
Alter your button definition to include Command="{StaticResource SaveCommand}", for example:
<Button x:Name="Button1" Content="Save" Command="{StaticResource SaveCommand}" />
In your Code Behind (.xaml.vb) put your function to call the save routine, such as:
Private Sub SaveAction(sender As Object, e As RoutedEventArgs)
Debug.Print("Call Save action here")
End Sub

How do I fix a custom border when dragging a form off the screen and back on?

I'm using a code to draw a custom dashed border around my textboxes and forms. If I grab the app and drag it off the screen and come back the border is all smeared and bad looking.
The only way I've been able to fix it is by using Me.Reload() event making the form reload which fixes it immediately. Which is alright but I'd rather it be fixed immediately almost to the point that you never even see it happen.
When I tried to add it into a timer it made the form blink really bad obviously.
Is there a way to use this code to detect when the app leaves the boundaries of the screen and just refresh the form only when the whole app returns within the boundaries of the monitor or monitors?
Public Function IsOnScreen(ByVal form As Form) As Boolean
Dim screens() As Screen = Screen.AllScreens
For Each scrn As Screen In screens
Dim formRectangle As Rectangle = New Rectangle(form.Left, form.Top, form.Width, form.Height)
If scrn.WorkingArea.Contains(formRectangle) Then
Return True
End If
Next
Return False
End Function
EDIT: I wanted to share the code I'm using to draw these borders in case it may actually be the issue.
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
' This is the override paint event that will allow us to draw all our borders
addBorders(e)
End Sub
Public Sub addBorders(ByVal e As PaintEventArgs)
For Each ctl As Control In Me.Controls
Select Case True
Case TypeOf (ctl) Is TextBox
ctl.AutoSize = False
ctl.Height = 19
Dim borderRectangle As Rectangle = New Rectangle(ctl.Location, ctl.Size)
borderRectangle.Inflate(1, 1)
ControlPaint.DrawBorder(e.Graphics, borderRectangle, ctl.ForeColor, ButtonBorderStyle.Dashed)
Case TypeOf (ctl) Is ComboBox, TypeOf (ctl) Is Button
Dim borderRectangle As Rectangle = New Rectangle(ctl.Location, ctl.Size)
borderRectangle.Inflate(1, 1)
ControlPaint.DrawBorder(e.Graphics, borderRectangle, ctl.ForeColor, ButtonBorderStyle.Dashed)
>>>ControlPaint.DrawBorder(e.Graphics, e.ClipRectangle, ctl.ForeColor, ButtonBorderStyle.Dashed)<<< This is the problem!
End Select
Next
End Sub
I've tried adding TypeOf (ctl) Is Form to the second Case and that does not work and I am not sure why!
Case TypeOf (ctl) Is ComboBox, TypeOf (ctl) Is Button, TypeOf (ctl) Is Form
This code works perfect for the comboboxes and the textboxes but it does not draw the border on the form.
I'm answering my own question because I've finally got it working.
First of all I changed how I was drawing the border around the form.
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
' This is the override paint event that will allow us to draw all our borders
addBorders(e)
If e.ClipRectangle.X = 0 Then
Dim dashValues As Single() = {3, 1, 3, 1}
Dim p As New Pen(btnExit.ForeColor, 1)
p.DashPattern = dashValues
e.Graphics.DrawRectangle(p, 0, 0, Me.Width - 1, Me.Height - 1)
End If
End Sub
Which worked perfect! There was still a small issue with going off the screen it would still mess the border up some so I added a Me.Refresh() code on the form_mouseup event so when the form is dropped it refreshes and fixes the problem.
Private Sub frmMain_MouseUp(sender As Object, e As MouseEventArgs) Handles Me.MouseUp
Me.Refresh()
End Sub
It's not exactly what I would have liked to have but it works way better this way than the other way.

Silverlight: Get first TextBox in DataTemplate

So far I have looked at several questions and answers for how to get a TextBox within a DataTemplate, but none of it is working for me.
I have xaml like so (minimal example). The data template is in my section for static resources, and the ItemsControl is in the content section:
<DataTemplate x:Key="GridTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="140" />
</Grid.ColumnDefinitions>
<sdk:IntegerTextBox DataField="Model.DataField" Width="90" SelectAllOnFocus="True" />
</Grid>
</DataTemplate>
<ItemsControl x:Name="MyControl" ItemsSource="{Binding MyList}" ItemTemplate="{StaticResource GridTemplate}" />
I need to be able set focus to the first IntegerTextBox in the Grid in the code behind. IntegerTextBox inherits the TextBox class.
I first tried writing my own method to recursively search through all UIElements for the first TextBox, but I found that the content within the DataTemplate isn't searchable in this way. The ItemsControl's children always return Nothing:
Private Function FirstTextBox(ByVal uiElement As UIElement) As TextBox
Dim textBox As TextBox = TryCast(uiElement, TextBox)
If textBox IsNot Nothing Then Return textBox
Dim panel As Panel = TryCast(uiElement, Panel)
If panel IsNot Nothing Then
For Each child As UIElement In panel.Children
textBox = FirstTextBox(child)
If textBox IsNot Nothing Then Return textBox
Next
End If
Dim itemsControl As ItemsControl = TryCast(uiElement, ItemsControl)
If itemsControl IsNot Nothing Then
For i As Integer = 0 To itemsControl.Items.Count
textBox = FirstTextBox(CType(itemsControl.ItemContainerGenerator.ContainerFromIndex(i), UIElement))
If textBox IsNot Nothing Then Return textBox
Next
End If
Return textBox
End Function
I have tried this, similar to here and here, but the ContentPresenter is Nothing:
Dim contentPresenter = CType(MyControl.ItemContainerGenerator.ContainerFromIndex(0), ContentPresenter)
Dim textbox As TextBox = CType(CType(contentPresenter.ContentTemplate.LoadContent(), Panel).Children.First(Function(c) TypeOf c Is TextBox), TextBox)
I tried getting the DataTemplate as shown here, then loading the content and searching the children for a TextBox as shown here, but it never finds a TextBox.
I have worked a couple days on this, but I cannot see what I am doing wrong. Is it some obvious mistake, or am I approaching the problem incorrectly? Thanks.
EDIT - This is how I got it to work by adding 100 ms delay:
Private Function FindDescendant(Of TDescendant As DependencyObject)(ByVal obj As DependencyObject) As TDescendant
Dim all = VisualTreeExtensions.GetVisualDescendants(obj)
Dim first = all.OfType(Of TDescendant)().FirstOrDefault()
Return first
End Function
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As ComponentModel.DoWorkEventArgs)
System.Threading.Thread.Sleep(100)
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As ComponentModel.RunWorkerCompletedEventArgs)
Dim firstTextBox = FindDescendant(Of IntegerTextBox)(MyControl)
If firstTextBox IsNot Nothing Then firstTextBox.Focus()
End Sub
Private Sub SetFocus()
Dim bw As New ComponentModel.BackgroundWorker
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
bw.RunWorkerAsync()
End Sub
You are approaching the problem incorrectly. Accessing the DataTemplate and searching for a TextBox will get you nowhere.
The template is only a blueprint, only when it is used somewhere (like for each item in your ItemsControl) its content is instantiated (once for each item).
One of several possible solutions to set focus to the first item's textbox:
In your code-behind add an eventhandler to the itemsControl:
MyControl.GotFocus += (sender, args) =>
{
var firstItemTextBox = MyControl.FindDescendant<IntegerTextBox>();
if ( firstItemTextBox != null ) firstItemTextBox.Focus();
};
some helper code:
//you need System.Windows.Controls.Toolkit.dll from the SilverlightToolkit for the class VisualTreeExtensions
using System.Windows.Controls.Primitives;
public static class ControlExtensions
{
public static TDescendant FindDescendant<TDescendant>(this DependencyObject element)
{
return element.GetVisualDescendants().OfType<TDescendant>().FirstOrDefault();
}
}
And btw: why do you wrap the IntergerTextBox in a separate Grid for each item?

How to make a control to be painted/refreshed properly

I have a control derived from checkbook which I called "SettingBooleanButton", but when any window or dialog is dragged over the control the control keeps signs of the drag
The next image shows the effect of dragging an application window over control
This is the code block that I have for OnPaint()
Public Class SettingBooleanButton
Inherits CheckBox
Private _settingSection As String
Private _settingName As String
Private _associatedSetting As Setting
Public Event StateChange(ByVal affectedSetting As Setting)
Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Appearance = Appearance.Button
FlatStyle = FlatStyle.Flat
TextAlign = ContentAlignment.MiddleCenter
AutoSize = False
End Sub
Public Property SettingSection As String
Get
Return _settingSection
End Get
Set(value As String)
_settingSection = value
End Set
End Property
Public Property SettingName As String
Get
Return _settingName
End Get
Set(value As String)
_settingName = value
End Set
End Property
''' <summary>
''' Sets a boolean value to indicate the initial checked state of the control.
''' </summary>
''' <value>
''' <c>true</c> to set it as [checked state]; otherwise, <c>false</c>.
''' </value>
Public Property CheckedState As Boolean
Get
Return Checked
End Get
Set(value As Boolean)
_associatedSetting = New Setting(_settingSection, _settingName, String.Empty)
RemoveHandler CheckedChanged, AddressOf StateChanged
Checked = value
SetText()
AddHandler CheckedChanged, AddressOf StateChanged
End Set
End Property
Private Sub StateChanged(sender As Object, e As EventArgs)
If IsNothing(_associatedSetting) Then
Return
End If
_associatedSetting.Value = Checked.ToString()
SetText()
RaiseEvent StateChange(_associatedSetting)
End Sub
Public Sub SetText()
If Checked Then
Font = New Font(Font.FontFamily, Font.Size, FontStyle.Bold)
ForeColor = Color.WhiteSmoke
Text = Resource.SettingBooleanButton_TrueState
Else
Font = New Font(Font.FontFamily, Font.Size, FontStyle.Regular)
ForeColor = SystemColors.ControlText
Text = Resource.SettingBooleanButton_FalseState
End If
End Sub
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
MyBase.OnPaint(e)
If Checked Then
ControlPaint.DrawBorder(e.Graphics, e.ClipRectangle, Color.Black, ButtonBorderStyle.Solid)
End If
End Sub
End Class
ControlPaint.DrawBorder(e.Graphics, e.ClipRectangle, ...)
Using e.ClipRectangle like this is a traditional bug in a Paint event handler. It is not a rectangle that matches the border you want to draw. It is only the part of the control that needs to be painted. Which is usually the entire control, but not always. Such as in your case when you drag a window across your control, only the part that is revealed needs to be repainted. So now you are painting the border in the wrong position, producing those black lines.
You only ever use the ClipRectangle if your painting code is expensive and you want to take the opportunity to skip that expensive code when it isn't needed anyway. Which is pretty rare, clipping in Windows is already pretty efficient.
You'll need to pass the actual rectangle of your border. Fix:
ControlPaint.DrawBorder(e.Graphics, Me.ClientRectangle, _
Color.Black, ButtonBorderStyle.Solid)
Sometimes the simplest solutions (or causes) are overlooked.
I have a panel with 15 buttons on it and each has an image. Depending on rows selected from a data grid they all might be enabled or disabled.
It all worked fine except toggling between enabled and disabled was taking 2+ seconds and caused lag when multi-selecting from the data grid.
Tried a few things, then I thought maybe it was something to do with the images.
The images were all in an imagelist and size was set to 24,24 which was a compromise between 32,32 and 16,16. I changed the size in the imagelist to 32,32 as that is the native size of all the images... and shazam!!! All the buttons are basically rendered instantly now. No idea ATM whether being small PNG images makes a difference... but I'm going to convert all the images I have to ICO format.
Also... as all my buttons are on a panel I enable/disable the panel which in turn enables and disables all the children on it.

Getting value of a dynamically generated text box in a listbox, Windows Phone 8

In my Windows Phone 8 app, I have a ListBox control. This list box contains a grid, which contains a pair of TextBlock (title of the field) and a TextBox (user input) controls. This list is generated based on the results of a service the app connects to. What I need to do is access each textbox within the list, and bring back the value of it. To do this, I have bound a unique ID of each item to the Tag property of the TextBox, and I am using the LostFocus event to capture the user input. Once this has been captured and added to a collection in the code behind, the data is processed when the user clicks a button under the list. This works fine for every item except the last one.
The problem is that the LostFocus doesn't work if the button is clicked. The button click method seems to take precedence over the textbox LostFocus method. So if there is only 1 item in the list, the value isn't recorded. Here is the code:
<ItemsControl x:Name="itmScreen4">
<TextBlock Margin="0,10,0,10">Fields</TextBlock>
<ListBox x:Name="lstCustom" ItemsSource="{Binding}" Visibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid x:Name="grdCustom">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="txtCustTitle" Text="{Binding Name}" Foreground="#4C4C4C" FontSize="18" Margin="0,15,0,0"></TextBlock>
<TextBox Tag="{Binding DataID}" x:Name="txtCust" Grid.Row="1" Style="{StaticResource TextBox}" Width="450" LostFocus="txtCust_LostFocus"></TextBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="btnSubmit" Content="Submit" Background="#404040"></Button>
</ItemsControl>
For the final item in the list (or if there's only one item in the list), the txtCust_LostFocus method doesn't get called when the btnSubmit method is called. Any ideas of how I can capture that final textbox value?
I have tried some other methods (casting a ListBoxItem and performing a FindName on it, for example), but haven't found anything that works. Google hasn't been much help either. Thanks in advance.
EDIT:
Here is the code behind. I am binding a custom class to the list as below.
Class definition here (I have removed some properties for readabality purposes):
Public Class CustomDataRequest
Public Sub New()
End Sub
Public Property ID As Integer
Public Property Name As String
End Class
Use in the code here:
Public Sub ShowCustomData()
Dim CustomDataList As New List(Of CustomDataRequest)()
For Each item In _CustomDataRequestList
If item.ID= _CurrentID Then
CustomDataList.Add(item)
End If
Next
lstCustom.ItemsSource = CustomDataList.ToArray
End Sub
The txtCust_LostFocus method is just capturing the fields at the minute. Once I can actually get it called, I can then add the data to the collection:
Private Sub txtCust_LostFocus(sender As Object, e As RoutedEventArgs)
Dim elem = DirectCast(sender, FrameworkElement)
Dim txt = DirectCast(elem.FindName("txtCust"), TextBox)
Dim text As String = txt.Text
Dim tag As String = txt.Tag
End Sub
The problem is that it never gets called once the button has been tapped:
Protected Sub btnSubmit_Tap(sender As Object, e As Input.GestureEventArgs) Handles btnSubmit.Tap
ValidateData()
End Sub
Here is the answer to this. The TextBox should have TwoWay binding mode set, to bind the text value to a corresponding Value property in the class. It should also have UpdateSourceTrigger set to Explicit for binding efficiency, as detailed here.
Public Class CustomDataRequest
Public Sub New()
End Sub
Public Property ID As Integer
Public Property Name As String
'New property here
Public Property Value As String
End Class
Textbox code:
<TextBox Text="{Binding Value, Mode=TwoWay, UpdateSourceTrigger=Explicit}" x:Name="txtCust" Grid.Row="1" BorderBrush="#BBBBBB" Style="{StaticResource TextBox}" Width="450" TextChanged="Textbox_Changed"></TextBox>
In the code behind, the following method should be called from the Textbox TextChanged (translated into VB):
Private Sub Textbox_Changed(sender As Object, e As TextChangedEventArgs)
Dim txt As TextBox = TryCast(sender, TextBox)
Dim bindingExpr As BindingExpression = txt.GetBindingExpression(TextBox.TextProperty)
bindingExpr.UpdateSource()
End Sub
This solves the problem of the textbox value not binding when the focus is still on the textbox. Thanks to venerik for the answer to this question.