Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Friday, December 10, 2010

Closing a WPF Window Through Your ViewModel

I ran into the issue of properly closing a Window (View) from my ViewModel a while ago.  How does one do this without breaking MVVM or adding too much complexity?  Since I’m developing my current project using Prism and my IUnityContainers are injected into my ViewModels, why not just inject this.Close() also?

/**** Window (View) ****/
...
public Window(IUnityContainer unityContainer)
{    
   InitializeComponent();    
   this.DataContext = new ViewModel(unityContainer, this.Close);
}
/**** End Window (View) ****/
 
/**** ViewModel ****/
public class ViewModel
{    
   public ViewModel(IUnityContainer unityContainwer, Action closeAction)    
   {        
      ...        
      CloseCommand = new DelegateCommand(closeAction);    
   }    
   
   public DelegateCommand CloseCommand    
   {        
      get;        
      private set;    
   }
}
/**** End ViewModel ****/

Does this break MVVM at all?  Is testability decreased because I injected the this.Close() method into my ViewModel?  I would argue not since I’ve used the Action delegate.  This seems to be the easiest/simplest way to do what I (and many others) want.  Now there may be some purists out there that don’t want any user entered code in the code-behind, but as you might have guessed, I’m no purist.

Wednesday, December 8, 2010

WPF, MVVM and TextBoxes

Overall, I like WPF and MVVM.  That’s not to say that I think MVVM is a perfect design pattern.  I find myself breaking the purity of MVVM sometimes because I think there’s a lot of compromise needed to do what I want to without adding complexity.

Anyway, one of the biggest gripes about MVVM and TextBoxes is that the data for bound TextBoxes is updated when the TextBox control loses focus.  But that makes sense, right?  Well, what if you’ve updated a TextBox and hit a MenuItem to save your data.  Guess what?  The TextBox doesn’t lose focus and your save takes the stale TextBox data.  Needless to say this is VERY frustrating…

One solution would be to move the focus off of the TextBox like so:

((System.Windows.UIElement)System.Windows.Input.Keyboard.FocusedElement).MoveFocus(new System.Windows.Input.TraversalRequest(System.Windows.Input.FocusNavigationDirection.Next));

You can call that before you actually execute your save command, but even that seems like a bit too much effort to do what should logically be done anyway… I’ve done the above in some of my projects, but there has to be some other way to do what I want…

Instead of using the vanilla Menu control, I turned to Infragistics’ xamMenu.  I was hoping that their menu would take into account something like the issue I was having with my TextBox data.  I was right… sort of.  It turns out that if I use their xamMenu like so:

<ig:xamMenu>
<ig:xamMenuItem Header="Save">
<ig:XamMenuItem.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding SaveCommand}"/>
</ig:XamMenuItem.InputBindings>
</ig:xamMenuItem>
</ig:xamMenu>

The above doesn’t do what I want either… After some experimenting, I found that if I use the vanilla MenuItem elements instead of Infragistics’ XamMenuItem elements, my TextBox data updates and saves.  It seems weird to me that using a combination of the Infragistics xamMenu and the vanilla MenuItem causes the data to update correctly, yet using all Infragistics’ elements doesn’t.

<shrug> In the end I ended up using vanilla Button elements in the xamMenu because using the vanilla MenuItem elements caused spacing issues:

<ig:xamMenu>    
<Button Command="{Binding SaveCommand}" Background="Transparent" BorderBrush="Transparent">
<StackPanel Orientation="Horizontal">
<Image Stretch="None" Source="{StaticResource Image}"/>
<TextBlock Background="Transparent" Margin="4,0,0,0" Text="Save"/>
</StackPanel>
</Button>
</ig:xamMenu>

Wednesday, October 20, 2010

VS2010 IDE vs Stand Alone Execution Weirdness (Mouse Wheel)

So I’m doing some ActiveX Interop with my WPF application.  As most people know, it’s more of a two step process with WPF since you have to host the ActiveX control inside of a WindowsFormsHost object before you can introduce it into a WPF Control or Window.

Anyway, I was testing my component in the IDE and it seemed to be working fine.  Next, I ran the application by itself because it’s faster than through the IDE and I found that my mouse wheel was not working.  What gives? Why would it work properly in the IDE, but not by itself?

I got frustrated and posted a question on the MSDN forums about it.  Since my ActiveX component has license restrictions, I couldn’t provide a proper sample application.  I then got the suggestion to host the Interop component in a Winforms application as a test.  My Winforms sample project works as it should inside and outside of the IDE.

I then tried to just start a simple WPF application with the Interop control and the same behavior occurs as in my main project: Mouse Wheel works in the IDE, but not stand alone.  At this point it seems like it’s a WPF/ActiveX Interop issue.

Another suggestion I got was to use Spy++ to see my component was responding to the Mouse Wheel messages.  I opened up Spy++, but before I did anything I noticed that my application was now handling the Mouse Wheel messages.  Now here’s what’s confusing me, as soon as I closed Spy++, my application stopped handling Mouse Wheel messages again.  Why does the simple fact of having Spy++ open make my ActiveX component work correctly???  I didn’t event start logging the component!

According to Spy++, my component was indeed handling Mouse Wheel messages as indicated by the log and also because the component was scrolling as it should… I’m waiting for a response to my forum post, hopefully someone can tell me what’s going on.

Thursday, September 30, 2010

My Prism Journey (Part 3)

It’s Me, Not You

WPF’s bare controls are decent, but not always as robust as people need or want.  I initially setup my Shell using the basic Grid component on a Window.  Of course I was and still am in the learning phase, but as my project becomes more “real”, I realize that I should probably start using the components that’ll actually be needed once my project hits our production environment.

Since I’ll be using floating panes, I turned to Infragistics.  We use their controls in many of our WinForms applications so it seemed logical to use them for my Prism project.  I figured this would be easy since Prism can inject views into any control that hosts an ItemControl or ContentControl

There was a bit of a learning curve to get Infragistics’ Dock Manager to work the way I wanted.  Although I was easily able to place ConentControls in the various dock regions, for some reason I couldn’t get the Dock Manager to fill its parent container.  This behavior wasn’t expected because the WinForms equivalent did fill in the area as expected.  I also followed the Getting Started sample from scratch in a new project and the control didn’t fill…

I did some searching and it looked like others were having the same issue.  The post I found was dated 2009 with no answer so I figured that this issue wasn’t resolved.  I was pretty irate that the expected behavior wasn’t implemented over a year later.  I even went so far as to gripe to the boss about it and asked if I could look at other companies’ components.

After my grumbling session, I took a step back.  Infragistics has been in the controls game a long time (remember Sheridan and VB pre .NET?), they couldn’t have let something like this slip.  I did some more searching and I found that all I needed was a single property: LayoutMode=”FillContainer”

Open hand facing upwards and forcefully place forehead into hand…

 

Views, ViewModels, And Events

Not all applications have simple UIs.  I have a registered library that handles some of my Region management based on certain requirements.  This manager will inject a View+ViewModel into a Region, nothing complicated here.  In my ViewModel I subscribe to certain events, which again is nothing complicated. 

I noticed something odd when I retrieved data through my application.  Every time I retrieved data, the time it took to display multiplied.  At first, I thought it was because my View wasn’t being properly removed from my Region. Nope, the ActiveView count is 0 as it should be.  I then placed break points in my data layer and found the culprit. 

The expected behavior is that if I request data, a new data layer is instantiated, and my data is returned.  However, every time I requested data, a data layer was instantiated, and the data was returned, but the previously instantiated data layer never got destroyed and was also retrieving data! 

This immediately made me look at the fact that I subscribed to a retrieval event, but never unsubscribed from it.  Prism’s event system must have still had a reference to the CallBack and was still calling it.  This explained the multiplicative time increase.  I ended up having to make sure that my Region manager library unsubscribes from any events when removing Views from my Regions

Friday, September 17, 2010

My Prism Journey (Part 1)

I’m not going to describe what Prism is because you can just do a search :)  I’m going to post my experiences in trying to leverage the Prism libraries to create a modular application.  The tutorials that come with the library are very helpful so people should definitely read the chm file for help.  I’m also trying to be as MVVM as possible so hopefully I don’t completely mangle my application.

One of the components that I’m adding to my application requires COM Interop… yay.  I did the obligatory aximp call on my ActiveX component, but for some reason I got a file not found exception whenever I tried to access the component at runtime.  Adding the references created by aximp didn’t seem to work.

I had to actually create a new WinForms control library, add the component to my toolbox, drag the component to the control, and then I was able to access the component at runtime.  No worries, I just copied the interop dlls and placed them in a folder so that those can be used instead of the aximp generated ones.

To include interop ActiveX components in your WPF application, you need to host the ActiveX component in a WinForms control.  No problem, add WindowsFormsIntegration so you can use the WindowsFormHost control in your WPF app/control.  You can then add the component to the WindowsFormHost.Child and it will display.  Pretty straight forward…

After getting the above sorted out, I got my interop control to display in the specified region in my shell.  However, it was displaying incorrectly… I set the ActiveX.Dock = Fill, but it wasn’t filling the region.  I sat there for a good hour or so trying to figure this one out… No matter what I did, it just wouldn’t fill the damn region

I then decided to create a little test program to see where the issue could possibly be.  I added a WindowsFormHost and a reference to the ActiveX component.  It filled fine!  Ok, maybe it’s because the region in my application is wrapped with a Tabcontrol.  I wrapped the WindowsFormHost with the TabControl and it still filled correctly.  Wait, to add a region to your shell, you need to specify what the region is hosted in, which is either an ItemsControl or ContentControl

It turns out that I used ItemsControls to host my regions (because the tutorial used those as well).  I then tried switching my region hosts to ContentControls and now my ActiveX component fills as it should… Sheesh.

Thursday, March 11, 2010

WinForms/WPF Interop ElementHost Not Drawing The WPF Control!

<Edit>

It turns out it wasn’t externalizing the WPF control that solved the issue, but rather changing my build configuration to Release instead of Debug.  When I added the externalized assembly, I set the configuration to Release.  I’m not sure exactly why a debug build would cause this behavior…

</Edit>

<Edit 2>

It seems to work properly on Windows XP regardless of the build configuration.  Might be a Windows 7 thing…

</Edit 2>

I’ve been doing a lot of WPF work lately and a lot of it has been with WinForms interoperating with WPF.  Today I noticed some odd behavior when I tried to add a WPF control to a WinForms ElementHost object…

Usually I put the WPF control in a separate assembly, but today I thought I didn’t need to since it was just a very simple control.  Immediately I saw that the WPF control wouldn’t draw itself properly for some reason.  It would only draw itself sometimes, but if i clicked on the WPF control and somehow hit a Button or ComboBox, the control would show up.

I then decided to try and put the simple control in its own assembly and that solved the problem… It looks like there’s some issues with including a WPF control in a WinForms project directly, in my case anyway.  I’m using Visual Studio 2008 on Win7 x86 and if anybody else has this type of issue, try doing what I did to resolve the issue.

Thursday, October 29, 2009

WPF Data Binding – ComboBox Items List

As I learn WPF I’m running into quite a few roadblocks, but I’m enjoying the fact that it’s a challenge and something new.  One of the things I’m trying to get used to is Data Binding to the WPF UI.  I’m also trying to learn WPF the MVVM way.  There are a lot of examples on how to do this on the web so I’ll just get to what got me stuck for a bit on ComboBox items binding.  Here’s some of my UI XAML and my code behind:

<DockPanel Margin="0" Height="42" Name="TopDockPanel" Width="auto" DockPanel.Dock="Top" LastChildFill="true">
<Grid Height="auto" Name="Grid1" Width="266" DockPanel.Dock="Left">
<Label Margin="7,7,0,9" HorizontalAlignment="Left" Width="71">Combo1:</Label>
<ComboBox ItemsSource="{Binding ComboList1}" Margin="84,9,22,13" SelectionChanged="Combo1_SelectionChanged" />
</Grid>
<Grid Height="auto" Name="Grid2" Width="281">

<Label HorizontalAlignment="Left" Margin="6,7,0,9" Width="89">Combo2:</Label>
<ComboBox ItemsSource="{Binding ComboList2}" Margin="101,9,20,13" IsEditable="False" />
</Grid>
<Grid Height="auto" Name="Grid3" Width="auto" />
</DockPanel>

Class SomeClass
Private _SomeClassVM As SomeClassViewModel = Nothing

Sub New()

' This call is required by the Windows Form Designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
_SomeClassVM = New SomeClassViewModel
Me.DataContext = _SomeClassVM
End Sub

Private Sub Combo1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.Windows.Controls.SelectionChangedEventArgs)
_SomeClassVM.Combo1Changed(sender)

End Sub
End Class


Nothing complicated, a simple MVVMish implementation (ish because there’s event handler code in the code behind file, I’ll be refactoring this as I go on).  My View Model implements the INotifyPropertyChanged Interface so that the UI can be notified of any property changes and update any data bindings as needed.  My View Model also exposes a couple of Public Properties: ComboList1 and ComboList2 which are List (Of String) objects:

Public Class SomeClassViewModel
Implements INotifyPropertyChanged

Private _ComboList1 As List(Of String)
Private _ComboList2 As List(Of String)

Public ReadOnly Property ComboList1() As List(Of String)
Get
Return _ComboList1
End Get
End Property

Public ReadOnly Property ComboList2() As List(Of String)
Get
Return _ComboList2
End Get
End Property

Sub New()
_ComboList1 = New List(Of String)

For Each item As String In ItemsList 'this is just a list of strings from anywhere
_ComboList1.Add(item)
Next

OnPropertyChanged("SQLServerList")

_ComboList2 = New List(Of String)

End Sub

Public Sub Combo1Changed(ByVal sender As System.Object)
_ComboList2.Clear()

For Each item As String In SomeOtherList 'this is just list of string from another place
_ComboList2.Add(sqlDatabase)
Next

OnPropertyChanged("ComboList2")
End Sub

Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

Private Sub OnPropertyChanged(ByVal propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class


Now, I thought that clearing and adding items to my ComboList2 object would suffice in propagating change notifications up to the UI…WRONG.  It seems there’s more to it than just clearing/adding to the list and raising the PropertyChanged event.  This implementation only notified the UI that ComboList2 changed once at the first time I changed ComboBox1’s selected item instead of each time.  What gives?

After some fiddling I found that I couldn’t use the current _ComboList2 object reference.  I had to actually set the _ComboList2 object to a new List for the UI to update:

Public Sub Combo1Changed(ByVal sender As System.Object)
Dim NewList As New List(Of String)

For Each item As String In SomeOtherList 'this is just list of string from another place
NewList.Add(item)
Next

_ComboList2 = NewList
OnPropertyChanged("ComboList2")
End Sub

There’s a more elegant way to solve the issue I had, which is to use an ObservableCollection instead of List (Of String) objects:

Public Class SomeClassModel

Private _ComboList1 As ObservableCollection(Of String) = Nothing
Private _ComboList2 As ObservableCollection(Of String) = Nothing

Public ReadOnly Property ComboList1() As ObservableCollection(Of String)
Get
Return _ComboList1
End Get
End Property

Public ReadOnly Property ComboList2() As ObservableCollection(Of String)
Get
Return _ComboList2
End Get
End Property

Sub New()
_ComboList1 = New ObservableCollection(Of String)

For Each item As String In ItemsList 'this is just a list of strings from anywhere
_ComboList1.Add(item)
Next

_ComboList2 = New ObservableCollection(Of String)

End Sub

Public Sub Combo1Changed(ByVal sender As System.Object)
_ComboList2.Clear

For Each item As String In SomeOtherList 'this is just list of string from another place
_ComboList2.Add(item)
Next

End Sub
End Class

You’ll notice a couple of things.  The first being that the View Model no longer needs INotifyPropertyChanged.  This is because ObservableCollection implements INotifyPropertyChanged (and INotifyCollectionChanged).  You’ll also notice that I was able to just clear/add to _ComboList2 without having to raise any events.  The UI updated as expected and I suspect it’s because when clearing or adding items to the ObservableCollection, properties such as count raise the PropertyChanged event properly.

Friday, May 8, 2009

WPF Canvas Coordinate System

I noticed that the Canvas class doesn’t use the Cartesian coordinate system for mapping points. For instance, here’s a list of points: 0, 0; 15, 5; 25, 18.  Here’s how these points look when drawn to a Canvas:

wpfCanvasPoints

The above image is fine if that’s what you want to do with the points, but that isn’t the result I wanted.  I had to add a ScaleTransform to the Canvas for it to look the way I wanted.  This can be done in XAML:

<Canvas Name="MainCanvas" Background="AliceBlue" Width="400" Height="200" >
    <Canvas.RenderTransform>
        <ScaleTransform CenterX="200" CenterY="100" ScaleX="1" ScaleY="-1" />
    </Canvas.RenderTransform>
</Canvas>

or programmatically (VB.NET here):

Dim YScale As New ScaleTransform

YScale.ScaleX = 1
YScale.ScaleY = -1
YScale.CenterX = MainCanvas.Width / 2
YScale.CenterY = MainCanvas.Height / 2

MainCanvas.RenderTransform = YScale

You’ll notice the CenterX and CenterY properties are set to the middle of the Canvas.  You can think of this as your pivot point when flipping the Canvas (The CenterX isn’t really needed here since we’re just flipping on the Y axis).The end result is what I was expecting:

wpfCanvasPointsYTransform