Using VB.net & WPF
I've converted code available at Overlaying Controls in WPF with Adorners from C# to VB.Net
Original C# Code
/// <summary>
/// Overlays a control with the specified content
/// </summary>
/// <typeparam name="TOverlay">The type of content to create the overlay from</typeparam>
public class OverlayAdorner<TOverlay> : Adorner, IDisposable where TOverlay : UIElement, new()
{
private UIElement _adorningElement; private AdornerLayer _layer; /// <summary> /// Overlay the specified element /// </summary> /// <param name="elementToAdorn">The element to overlay</param> /// <returns></returns> public static IDisposable Overlay(UIElement elementToAdorn) { return Overlay(elementToAdorn, new TOverlay()); }
/// <summary>
/// Overlays the element with the specified instance of TOverlay
/// </summary>
/// <param name="elementToAdorn">Element to overlay</param>
/// <param name="adorningElement">The content of the overlay</param>
/// <returns></returns>
public static IDisposable Overlay(UIElement elementToAdorn, TOverlay adorningElement)
{
var adorner = new OverlayAdorner<TOverlay>(elementToAdorn, adorningElement);
adorner._layer = AdornerLayer.GetAdornerLayer(elementToAdorn);
adorner._layer.Add(adorner);
return adorner as IDisposable;
}
private OverlayAdorner(UIElement elementToAdorn, UIElement adorningElement)
: base(elementToAdorn)
{
this._adorningElement = adorningElement;
if (adorningElement != null)
{
AddVisualChild(adorningElement);
}
Focusable = true;
}
protected override int VisualChildrenCount
{
get { return _adorningElement == null ? 0 : 1; }
}
protected override Size ArrangeOverride(Size finalSize)
{
if (_adorningElement != null)
{
Point adorningPoint = new Point(0, 0);
_adorningElement.Arrange(new Rect(adorningPoint, this.AdornedElement.DesiredSize));
}
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
if (index == 0 && _adorningElement != null)
{
return _adorningElement;
}
return base.GetVisualChild(index);
}
public void Dispose()
{
_layer.Remove(this);
}
}
VB.Net Code (Converted by Me)
Public Class OverlayAdorner(Of TOverlay As {UIElement, New})
Inherits Adorner
Implements IDisposable
Private _adorningElement As UIElement
Private _layer As AdornerLayer
Public Shared Function Overlay(elementToAdorn As UIElement, adorningElement As TOverlay) As IDisposable
Dim adorner = New OverlayAdorner(Of TOverlay)(elementToAdorn, adorningElement)
adorner._layer = AdornerLayer.GetAdornerLayer(elementToAdorn)
adorner._layer.Add(adorner)
Return TryCast(adorner, IDisposable)
End Function
Private Sub New(elementToAdorn As UIElement, adorningElement As UIElement)
MyBase.New(elementToAdorn)
Me._adorningElement = adorningElement
If adorningElement IsNot Nothing Then
AddVisualChild(adorningElement)
End If
Focusable = True
End Sub
Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
Get
Return If(_adorningElement Is Nothing, 0, 1)
End Get
End Property
Protected Overrides Function ArrangeOverride(finalSize As Size) As Size
If _adorningElement IsNot Nothing Then
Dim adorningPoint As New Point(0, 0)
_adorningElement.Arrange(New Rect(adorningPoint, Me.AdornedElement.DesiredSize))
End If
Return finalSize
End Function
Protected Overrides Function GetVisualChild(index As Integer) As Visual
If index = 0 AndAlso _adorningElement IsNot Nothing Then
Return _adorningElement
End If
Return MyBase.GetVisualChild(index)
End Function
Public Sub Dispose() Implements IDisposable.Dispose
_layer.Remove(Me)
End Sub
End Class
Now I've created MainWindow & an User Control UserControl1 in my test project and trying code
Using OverlayAdorner(Of UserControl1).Overlay(G1)
'Err in First Line Itself
End Using
Error Argument not specified for parameter 'adorningElement' of 'Public Shared Function Overlay(elementToAdorn As System.Windows.UIElement, adorningElement As TOverlay) As System.IDisposable'.
What is Wrong Here
Below code block given on blog post you are referring to has incorrect usage shown.
using (OverlayAdorner<ProgressMessage>.Overlay(LayoutRoot))
{
// Do some stuff here while adorner is overlaid
}
Should be (in C#):
using (OverlayAdorner<ProgressMessage>.Overlay(LayoutRoot, this)) // Here I assume `this` is somehow `UserControl`'s object
{
// Do some stuff here while adorner is overlaid
}
In VB.NET
Using OverlayAdorner(Of UserControl).Overlay(G1, UserControl1)
' Do some stuff here while adorner is overlaid
End Using
Also note that: OverlayAdorner(Of UserControl1) should be OverlayAdorner(Of UserControl) because T here should be Type's name not name of object instance.
I hope that helps.
Some preaching
From this question of yours I get hint that you need to work yourself on language more. Give more time to VB.NET or C#.NET and use intellisense in Visual Studio to get yourself familiar with the class library and parameter types to be passed into methods you use. This will help you working in new class libraries which have some to no documentation.
I use this shortcut more when working in new class libaries. Type ctrl+k then ctrl+i. Generally said as ctrl+k,i
Update
Based on recent comment, Check the actual usage below:
XAML snippet:
<Window x:Class="WpfTestApp.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 x:Name="G1">
<UserControl x:Name="ucMyControl"></UserControl>
</Grid>
</Window>
Code behind snippet:
Using OverlayAdorner(Of UserControl).Overlay(G1, ucMyControl)
' Do some stuff here while adorner is overlaid
End Using
Related
I am using VB/wpf and SQL Server. I am NOT using mvvm, a datatable, a button, a dataset or a windows form. Under those conditions and a couple of days of searching, I cannot find the final step to saving the data from a grid.
My database table tracks donations; it has the usual, expected fields. Here is what I have so far:
XAML (snippet):
<Window.Resources>
<CollectionViewSource
Filter="Filter_By_Member"
x:Key="cvsDonations">
</CollectionViewSource>
.....
<DataGrid
AutoGenerateColumns="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
CanUserResizeRows="False"
CanUserDeleteRows="True"
FontSize="13"
FontWeight="Normal"
ItemsSource="{Binding Source={StaticResource cvsDonations}}"
Name="dgDonations"
RowHeaderWidth="20"
SelectionUnit="CellOrRowHeader"
SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Path=DateDue, Converter={StaticResource conDate}, StringFormat='MMM d, yyyy'}"
ElementStyle="{StaticResource styDateBlock}"
Header="Date Due"
IsReadOnly="True"
Width="90">
</DataGridTextColumn>
.....
My window contains a ComboBox and the grid. The rows in the grid depend on what is selected in the ComboBox. Here is the code that does that.
Private Sub Change_Member(sender As Object, e As SelectionChangedEventArgs) Handles cboMembers.SelectionChanged
cvsDonations.View.Refresh()
End Sub
Private Sub Filter_By_Member(sender As Object, e As FilterEventArgs)
Dim PersonID As Long = CLng(cboMembers.SelectedValue)
Dim d As Donation = DirectCast(e.Item, Donation)
If d.PersonID = PersonID Then
e.Accepted = True
Else
e.Accepted = False
End If
End Sub
So far everything works. What I haven't figured out is how to automatically update the underlying database (using stored procedures which are already written and working). More specifically, I cannot figure out what event to watch for and how to implement that event in code.
The underlying collection is an ObservableCollection(Of Donation) and the Donation class has implemented INotifyPropertyChanged. I've considered watching the property changed but that event gets fired every time the grid is populated so that doesn't seem like the right approach, besides I can't figure out how to actually raise the appropriate event even if that is the way to go.
I've tried using the RowEditChanging event which seems to be ALMOST what I want but it is fired before any edits are submitted so the donation the row contains has the old value when this event is fired.
What is the right approach and how should it be implemented given my overall set up?
I suggest doing it this way -
Your Donation class should have a flag field, let's say Is_Dirty which keeps track of whether the object has modifications or not.
When you initially load items in your collection, make sure Is_Dirty is set to false. When user makes edit on grid and property notification is fired, set the dirty flag (from property setter).
For saving, I suggest having a separate button, clicking on which you go through all dirty objects from your collection and pass updates to database.
By binding to dirty flag, you can also change the appearance of modified rows for user to identify modified rows.
You can also maintain a State property, to know whether the object is Added, Updated or Deleted.
Typically, I put all this functionality in a base class from which all my model classes (e.g. Donation) derives.
Some snippet to get you going...
public abstract class ObservableDomainObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _isDirty;
public ObservableDomainObject()
{
_state = ObjectState.Added;
_isDirty = true;
}
private ObjectState _state;
/// <summary>
/// Get or Set
/// </summary>
public ObjectState State
{
get { return _state; }
set
{
if (_state != value)
{
_state = value;
RaisePropertyChanged();
}
}
}
public void SetDirty(Boolean isDirty)
{
this.IsDirty = isDirty;
}
/// <summary>
/// Check if the object is modified.
/// </summary>
public bool IsDirty
{
get { return _isDirty; }
set
{
if (_isDirty != value)
{
if (_state != ObjectState.Added && _state != ObjectState.Deleted)
{
this.State = value ? ObjectState.Modified : ObjectState.Unchanged;
}
_isDirty = value;
RaisePropertyChanged();
}
}
}
protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
// Change Dirty flag when property changed.
if (propertyName != "IsDirty")
SetDirty(true);
}
}
public enum ObjectState
{
/// <summary>
/// New Record
/// </summary>
Added = 0,
/// <summary>
/// Unchanged after the load
/// </summary>
Unchanged,
/// <summary>
/// Modified
/// </summary>
Modified,
/// <summary>
/// Deleted
/// </summary>
Deleted
}
I'm filling the itemssource of a WPF combobox in code-behind with a datatable containing the columns "Listkey" and "Listvalue" like that:
SetElementProperty(element, "ItemsSource", (new ListtablesRead()).ReadListtable(changeTextProperties.SelectedListTable).DefaultView);
SetElementProperty(element, "DisplayMemberPath", "Listvalue");
SetElementProperty(element, "SelectedValuePath", "Listkey");
SetElementProperty is a method which checks by reflection, if the frameworkelement (in that case the combobox) has the given property and sets it.
Then i want to serialize the control with XmlWriter.
So i wrote a converter class for the type DataRowView:
using System;
using System.ComponentModel;
using System.Data;
using System.Windows;
using System.Windows.Markup;
namespace WPFDesignerConverterLibrary
{
public class DataRowViewConverter : ExpressionConverter
{
/// <summary>
/// Finds out if the converter can convert an expression-object to the given destinationtype.
/// </summary>
/// <param name="context">An ITypeDescriptorContext-interface which provides a context for formatting.</param>
/// <param name="destinationType">A type-class which represents the target-type of the conversion.</param>
/// <returns>Returns an object of type bool. True = the destinationtype can be converted.</returns>
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
return true;
return false;
}
/// <summary>
/// Converts the expression to the given destinationtype.
/// </summary>
/// <param name="context">An ITypeDescriptorContext-interface which provides a context for formatting.</param>
/// <param name="culture">The System.Globalization.CultureInfo which is actually used as culture.</param>
/// <param name="value">The object to convert.</param>
/// <param name="destinationType">A type-class which represents the target-type of the conversion.</param>
/// <returns></returns>
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value,
Type destinationType)
{
if (destinationType == typeof(MarkupExtension))
{
DataRowView datarowview = value as DataRowView;
if (datarowview == null)
throw new Exception();
return datarowview.Row;
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}
This converter works and produces the following lines in the serialized XML:
<sd:DataRow RowError="">
<sd:DataRow.ItemArray>
<x:Array Type="s:Object" xml:space="preserve"><s:String>01</s:String><s:String>Ersttest </s:String></x:Array>
</sd:DataRow.ItemArray>
</sd:DataRow>
<sd:DataRow RowError="">
<sd:DataRow.ItemArray>
<x:Array Type="s:Object" xml:space="preserve"><s:String>02</s:String><s:String>Wiederholungstest </s:String></x:Array>
</sd:DataRow.ItemArray>
</sd:DataRow>
<sd:DataRow RowError="">
<sd:DataRow.ItemArray>
<x:Array Type="s:Object" xml:space="preserve"><s:String>03</s:String><s:String>Konstanzprüfung </s:String></x:Array>
</sd:DataRow.ItemArray>
</sd:DataRow>
But when i try to reload the serialized XML i get an error message, which says, that no standard constructor for the type DataRow was found.
What's going wrong?
And further: To set the itemssource of the combobox with a datatable's defaultview is the simplest way to do this, but can it be that i have to go another way?
InnerException:
=-2146233069 HResult
Message=For the type "System. Data. DataRow" no standard constructor was found. The type can be provided with the argument or the FactoryMethod directive.
Source=System. Xaml
StackTrace:
at system. Xaml. Pattern. XamlTypeInvoker. DefaultCtorXamlActivator. EnsureConstructorDelegate (XamlTypeInvoker type)
at system. Xaml. Pattern. XamlTypeInvoker. CreateInstance (Object [] of argument)
at MS.Internal. Xaml. Run time. ClrObjectRuntime. CreateInstanceWithCtor (XamlType xamlType, Object [] args)
at MS.Internal. Xaml. Run time. ClrObjectRuntime. CreateInstance (XamlType xamlType, Object [] args)
Meanwhile i have found another way.
I'm using a dummy-class called KeyAndValue:
/// <summary>
/// Dummy class with key and value properties for construction of comboboxitems.
/// </summary>
public class KeyAndValue
{
/// <summary>
/// Gets or sets the key.
/// </summary>
public string Key { get; set; }
/// <summary>
/// Gets or sets the value.
/// </summary>
public string Value { get; set; }
}
That class helps to fill the items-collection of the combobox:
foreach (System.Data.DataRow row in table.Rows)
{
// Add new pairs with Listkey and Listvalue as content to the Items-collection.
customComboBox.Items.Add(new KeyAndValue() { Key = row["Listkey"].ToString(), Value = row["Listvalue"].ToString() });
}
That's a little bit like using KeyValuePair with one diffenrence: A generic list like KeyValuePair cannot be serialized.
But my dummy-class does it.
What you need is to have a class with parameterless contrsuctor aka Default Constructor that will allow Serialization.
Step 1.
Create a class to hold your database results.
You can do this through manual code or you could use DataSet.
Step 2.
After you created the class or used the dataset serialize it.
FileStream fs = new FileStream(savePath + "\\" + a.Location + ".bin", FileMode.Create);
BinaryFormatter serializer = new BinaryFormatter();
try
{
serializer.Serialize(fs, a);
}
catch (Exception e)
{
//handle your fail;
}
finally
{
fs.Close();
}
or deserialize it:
List<Model.YOURCLASS> _l = new List<Model.YOURCLASS>();
string[] files = Directory.GetFiles(pathToSearch);
FileStream fs;
BinaryFormatter deserializer = new BinaryFormatter();
foreach (var a in files)
{
if(Path.GetExtension(a) == ".bin")
{
fs = new FileStream(a, FileMode.Open);
_l.Add((Model.YOURCLASS)deserializer.Deserialize(fs));
}
}
return _l;
The type that your routine returns doesn't have to be a DataTable your ListView will automatically use any collection you pass it, even an array[]
I have a parameterised constructor in My Application. I want to add controls dynamically to my silverlight Child Control Page. But it gives NullReferenceException.
I can't find out why it returns null.Can any help me with this situation?
public PDFExport(FrameworkElement graphTile1, FrameworkElement graphTile2,FrameworkElement graphTile3)
{
Button btnGraph1 = new Button();
string Name = graphTile1.Name;
btnGraph1.Content = Name;
btnGraph1.Width = Name.Length;
btnGraph1.Height = 25;
btnGraph1.Click += new RoutedEventHandler(btnGraph1_Click);
objStack.Children.Add(btnGraph1);
LayoutRoot.Children.Add(objStack); // Here am getting null Reference Exception
_graphTile1 = graphTile1;
_graphTile2 = graphTile2;
_graphTile3 = graphTile3;
}
Thanks.
I guess objStack is a stackpanel declared in your XAML?
Be aware that the UI component of your xaml are build by the call to InitializeComponent.
Thus objStack will not exist until you call InitializeCOmponent() in your constructor.
Also, you should know that the call to InitializeComponent is asynchronous, so you code should look like something like that:
private readonly FrameworkElement _graphTile1;
private readonly FrameworkElement _graphTile2;
private readonly FrameworkElement _graphTile3;
public PDFExport(FrameworkElement graphTile1, FrameworkElement graphTile2, FrameworkElement graphTile3)
{
_graphTile1 = graphTile1;
_graphTile2 = graphTile2;
_graphTile3 = graphTile3;
}
private void PDFExport_OnLoaded(object sender, RoutedEventArgs e)
{
Button btnGraph1 = new Button();
string Name = _graphTile1.Name;
btnGraph1.Content = Name;
btnGraph1.Width = Name.Length;
btnGraph1.Height = 25;
btnGraph1.Click += new RoutedEventHandler(btnGraph1_Click);
objStack.Children.Add(btnGraph1);
LayoutRoot.Children.Add(objStack);
}
Hope it helps.
As per my research i got that, why it raises an exception: Because there is no
InitializeComponent() in My Constructor and am not calling parent constructor.
That is the reason it raises Exception.
Just Add InitializeComponent() to the code, simple
i have created a custom user control which im using on my main xaml control:
<Controls:CustomControl Width="200" Height="20"
TotalCount="{Binding TotalRecordCount}" SuccessCount="{Binding ValidationCount}" ErrorCount="{Binding ValidationErrorCount}" Margin="0,5,0,0" HorizontalAlignment="Left">
</Controls:CustomControl>
I wanted to make the private variables of my custom usercontrol being ErrorCount,SuccessCount and total count(which are of type int32) Bindable so i can bind values to them. Right now when i try to bind it to my item source it gives the following error e exception message is "Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Int32'
Many thanks,
Michelle
You need to implement the Properties using DependencyProperty don't use private variables to hold these values. Here is an example:-
#region public int SuccessCount
public int SuccessCount
{
get { return (int)GetValue(SuccessCountProperty); }
set { SetValue(SuccessCountProperty, value); }
}
public static readonly DependencyProperty SuccessCountProperty =
DependencyProperty.Register(
"SuccessCount",
typeof(int),
typeof(CustomControl),
new PropertyMetadata(0, OnSuccessCountPropertyChanged));
private static void OnSuccessCountPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomControl source = d as CustomControl;
int value = (int)e.NewValue;
// Do stuff when new value is assigned.
}
#endregion public int SuccessCount
In order for a property to be "Bindable", meaning that you can set it using DataBinding, that property needs to be a Dependency Property. For more info on Dependency Properties, please check this MSDN article.
hope this helps :)
When a user clicks in certain places in my control, I want to change the color of some rows and columns in my grid, then fade it back to the normal color, within say 500ms or so. I haven't decided whether to use Winforms or WPF yet, so advice in either of those technologies would work. Thank you.
Edit: I understand I could do this by just calling Paint in a loop within the click event, properly setting the drawing parameters. However I believe that would block the UI, and I would like to be more responsive than that.
WPF has very good support for animations. Animations are supported from both xaml and code behind, so you should be able to achieve any look that you are going for.
The MSDN Animation Overview for WPF looks to have a lot of good information for getting you started.
Here is one way you could handle the fade:
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsApplication1
{
public class FadeForm : Form
{
private Timer fadeTimer;
private Panel fadePanel;
private Button fadeButton;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose( bool disposing )
{
if ( disposing && ( components != null ) )
{
components.Dispose();
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.fadePanel = new System.Windows.Forms.Panel();
this.fadeButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// fadePanel
//
this.fadePanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.fadePanel.Location = new System.Drawing.Point( 4, 8 );
this.fadePanel.Name = "fadePanel";
this.fadePanel.Size = new System.Drawing.Size( 276, 104 );
this.fadePanel.TabIndex = 0;
//
// fadeButton
//
this.fadeButton.Location = new System.Drawing.Point( 104, 116 );
this.fadeButton.Name = "fadeButton";
this.fadeButton.Size = new System.Drawing.Size( 75, 23 );
this.fadeButton.TabIndex = 1;
this.fadeButton.Text = "Fade";
this.fadeButton.UseVisualStyleBackColor = true;
this.fadeButton.Click += new System.EventHandler( this.HandleFadeButtonClick );
//
// FadeForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size( 284, 142 );
this.Controls.Add( this.fadeButton );
this.Controls.Add( this.fadePanel );
this.Name = "FadeForm";
this.Text = "Fade Form";
this.ResumeLayout( false );
}
#endregion
public FadeForm()
{
InitializeComponent();
this.fadeTimer = new Timer();
}
private void HandleFadeButtonClick( object sender, EventArgs e )
{
this.fadeTimer.Tick += new EventHandler( HandleFadeTimerTick );
this.fadePanel.BackColor = Color.Red;
this.fadeTimer.Interval = 100;
this.fadeTimer.Start();
}
void HandleFadeTimerTick( object sender, EventArgs e )
{
Color panelColor = this.fadePanel.BackColor;
if ( panelColor.A > 0 )
{
this.fadePanel.BackColor =
Color.FromArgb(
Math.Max( panelColor.A - 20, 0 ),
panelColor.R, panelColor.G, panelColor.B );
}
else
{
this.fadeTimer.Stop();
}
}
}
}
Unfortunately, this approach doesn't seem to work with rows in a DataGridView. I don't know the reason, but the color doesn't show at all if the alpha component of the color isn't 255. If you can find a way around that, this code might help.
At its simplest, a fade effect like this just requires a timer of some sort that gradates the color back towards normal with each tick. The faster the time, the more discrete colors you will display from start to finish, and the smoother the overall effect will be (WPF may have something built-in to do this).
You definitely do not want to repaint in a loop. As you pointed out this will block the UI, and also you would not be able to control how long the loop takes (different machines will render the same number of steps from highlight color to normal in different lengths of time).