Best wpf questions in October 2010

C# WPF WebBrowser alternative

13 votes

I need a web browser inside my .NET 3.5 application and I don't want to use the default webbrowser (because it feels buggy and slow). I tried Chris Cavanagh's chromium but I just couldn't get it to work... are there any easier to use alternatives? I'm looking for something very simple and light on the computer, it will host a webstore front if that matters.

I've had luck using GeckoFX in a C# win form application. Here is the getting started article, which explains what dependencies you'll need to install to get it up and running.

How to set and change the culture in WPF

12 votes

I have a .NET 4.0 WPF application where the user can change the language (culture) I simply let the user select a language, create a corresponding CultureInfo and set:

Thread.CurrentThread.CurrentCulture = cultureInfo;
Thread.CurrentThread.CurrentUICulture = cultureInfo;

In the C# code this works fine. However in the WPF controls the culture is still en-US. This means for example that dates will be shown in the US format instead of whatever is correct for the current culture.

Apparently, this is not a bug. According to MSDN and several blog posts and articles on StackOverflow the WPF language does not automatically follow the current culture. It is en-US until you do this:

FrameworkElement.LanguageProperty.OverrideMetadata(
    typeof(FrameworkElement),
    new FrameworkPropertyMetadata(
        XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag)));

See for example StringFomat Localization problem.

I do not completely understand what is going on here. It seems the Language property on all frameworkelements is set to the current culture. Anyway, it works. I do this when the application starts up and now all controls works as expected, and e.g. dates is formatted according to the current culture.

But now the problem: According to MSDN FrameworkElement.LanguageProperty.OverrideMetadata can only be called once. And indeed, if I call it again (when the user changes the language) it will throw an exception. So I haven't really solved my problem.

The question: How can I reliably update the culture in WPF more than once and at any time in my applications life cycle?

(I found this when researching: http://www.nbdtech.com/Blog/archive/2009/03/18/getting-a-wpf-application-to-pick-up-the-correct-regional.aspx and it seems he has something working there. However, I can't imagine how to do this in my application. It seems I would have to update the language in all open windows and controls and refresh all existing bindings etc.)

I never found a way to do exactly what I asked for in the question. In my case I ended up solving it by having all my usercontrols inherit from a superclass that contained this:

/// <summary>
///   Contains shared logic for all XAML-based Views in the application. Views that extend this type will have localization built-in.
/// </summary>
public abstract class ViewUserControl : UserControl
{
    /// <summary>
    ///   Initializes a new instance of the ViewUserControl class.
    /// </summary>
    protected ViewUserControl()
    {
        // This is very important! We make sure that all views that inherit from this type will have localization built-in.
        // Notice that the following line must run before InitializeComponent() on the view. Since the supertype's constructor is executed before the type's own constructor (which call InitializeComponent()) this is as it should be for classes extending this
        this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.IetfLanguageTag);
    }
}

When the user changes the language I then create new instances of any usercontrols that are currently running.

This solved my problem. However, I would still like a way to do this "automatically" (i.e. without having to keep track of any instantiated objects).

Future desktop LOB apps - WPF, Silverlight or... HTML5?

9 votes

The Silverlight strategy has shifted to Windows Phone, and for cross-platform web development, it's HTML 5.

http://www.zdnet.com/blog/microsoft/microsoft-our-strategy-with-silverlight-has-shifted/7834

I'm just starting to get comfortable with the idea of developing desktop line-of-business apps with Silverlight out-of-browser (over WPF), but I'm not sure what this strategic shift means for the desktop.

Should I shift back to considering WPF instead, thinking that when HTML5 matures, they might abandon Silverlight eventually?

Also mentioned was this quote:

Silverlight also has some “sweet spots” in media and line-of-business applications, he said.

News media frequently twist facts, or mention only selected facts, as a way of making the story better. Silverlight is not dying off, and the HTML5 spec has not been ratified.

One could also draw the conclusion from that article that MS are doing their best to manipulate the HTML5 spec to suit themselves and we will end up with a situation like we had in the past: everyone implements the same spec in a different way and you need to code workarounds for each major browser. Because of this wrangling (which other vendors will no doubt also be doing) it may take a long time for the spec to be fully ratified.

Having said that, WPF is not subject to any of this uncertainty - but it still isn't cross platform. So you have to ask yourself these questions:

  • what is the expected lifespan of your LOB app?
  • how long before it gets a major rewrite?
  • is it possible to release it in several different technologies to suit different setups?

LOB apps tend to be quite tightly spec'd and controlled to specific setups, so a long term change of focus for Silverlight may not affect you that much at all.


EDIT: the day after i answered this question, Colin Eberhardt wrote this very good blog post: Does HTML5 mean the end is in sight for Silverlight? which covers this exact news story / quote. Colin also references this follow up blog post from Bob Muglia1 which elaborates on what he was quoted as saying. I think you can safely conclude that it is absolutely fine to develop your LOB app in Silverlight if you want to :)

1 President of the Server and Tools Division at Microsoft

WPF ObservableCollection Edit Mode

8 votes

I am using observable collections all around my applications. My problem is that when i use a popup window for editing those entities, my bound lists are getting changed when the user changes those corresponding fields in the window.

How could i simply freeze the observable changes norifications, and release them only when the entity is saved?

Thanks, Oran

You could make a deep copy of the object you want to edit. This way, you can act on the copy while editing, without interfering with the original that remains in the list. Once you`re done editing, you can replace the original by the edited version or rollback.

MVVM. Is adding code to View justified in some cases?

7 votes

I have a View which has a list of items bound to my ViewModel (MVVM pattern).

Let's say it looks like that:

<ScrollViewer Width="Auto" Height="Auto">
    <ItemsControl ItemsSource="{Binding Path=MessageLog}" 
                  Grid.IsSharedSizeScope="True"                     
                  ScrollViewer.CanContentScroll="True">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="150" SharedSizeGroup="FullName"/>
                        <ColumnDefinition Width="*" SharedSizeGroup="MessageLog"/>
                    </Grid.ColumnDefinitions>                                   
                    <StackPanel>
                        <TextBlock Text="{Binding Path=PostedBy.FullName}" />
                        <TextBlock Text="{Binding Path=DatePosted}" />
                    </StackPanel>
                    <TextBlock Grid.Column="1" Text="{Binding Path=MessageLog}"/>
                </Grid>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</ScrollViewer>

When user adds something to MessageLog (there is a property MessageLog in VM) I want to automatically scroll to the most recent item.

In other words, I just want to move the scrollbar automatically when user type a message and hit the enter (something like Skype does).

Binding on MessageLog works as expected and item is updated on the view. (I'm happy about that and I want to leave it like that)

I am wondering if using MVVM pattern approach, can I still implement an auto scroll in code behind file of the View? It seems to be quite logic as scrolling behavior has nothing to do with the VM and ViewModel doesn't know anything about the View. Is it right? Am I going right way or I am missing something?

Generally speaking, when adding an implementation to a View makes sense?

Yes, this is perfectly acceptable. Since the logic here is 100% View related, there is no problem with adding it to the View.

MVVM is about separating your Application logic from your View logic, not necessarily about stripping 100% of the code out of your View.

That being said, there are alternatives to code behind for this. Attached properties (or Behaviors) are a great option for tasks like these - they have the large advantage of being reusable in other Views later, so you don't reinvent this later if you decide you want the same behavior in other parts of your User Interface.

In a button's control template, how can I set the color of contained text?

6 votes

Using Silverlight 4 & WPF 4, I'm trying to create a button style that alters the text color of any contained text when the button is mouseover'd. Since I'm trying to make this compatible with both Silverlight & WPF, I'm using the visual state manager:

<Style TargetType="{x:Type Button}">
<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="Button">
            <Border x:Name="outerBorder" CornerRadius="4" BorderThickness="1" BorderBrush="#FF757679">
                <VisualStateManager.VisualStateGroups>
                    <VisualStateGroup x:Name="CommonStates">
                        <VisualState x:Name="Normal" />
                        <VisualState x:Name="MouseOver">
                            <Storyboard>
                                <ColorAnimation Duration="0" To="#FFFEFEFE"
                                                Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
                                                Storyboard.TargetName="contentPresenter"/> 
                            </Storyboard>
                        </VisualState>
                    </VisualStateGroup>
                </VisualStateManager.VisualStateGroups>
                <Grid>
                    <Border x:Name="Background" CornerRadius="3" BorderThickness="1" BorderBrush="Transparent">
                        <Grid>
                            <ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"/>
                        </Grid>
                    </Border>
                </Grid>
            </Border>
        </ControlTemplate>
    </Setter.Value>
</Setter>

Since this is a template for a regular old button, I know there's no guarantee that there even is a textblock inside of it, and at first I wasn't sure this was even possible. Curiously, the text color does change if the button is declared like:

<Button Content="Hello, World!" />

but it does not change if the button is declared like:

<Button>
    <TextBlock Text="Hello, World!" /> <!-- Same result with <TextBlock>Hello, World </TextBlock> -->
</Button>

Even though the visual tree (when inspected in snoop) is identical (Button -> ContentPresenter -> TextBlock), with the caveat that the textblock created in the 1st version has it's data context set to "Hello, World", whereas the textblock in the second version merely has its text property set. I'm presuming this has something to do with the order of control creation (the first version the button creates the TextBlock, in the second version the textblock might be created first? Really not sure on this).

In the course of researching this, I've seen some solutions that work in Silverlight (like replacing the ContentPresenter with a ContentControl), but that won't work in WPF (program actually crashes).

Since this is in the button's control template, and I'd like to use the VSM if possible, I think that also rules out explicitly changing the Button's own Foreground property (I don't know how I would access that from within the template?)

I'd really appreciate any help, advice anyone could give.

So after some more thinking, the solution I've ultimately arrived at is to add an attached property to the ContentPresenter element within the button's control template. The attached property accepts a Color and when set examines the visual tree of the content presenter for any TextBlocks, and in turn sets their Foreground properties to the value passed in. This could obviously be expanded/made to handle additional elements but for now it works for what I need.

public static class ButtonAttachedProperties
    {
        /// <summary>
        /// ButtonTextForegroundProperty is a property used to adjust the color of text contained within the button.
        /// </summary>
        public static readonly DependencyProperty ButtonTextForegroundProperty = DependencyProperty.RegisterAttached(
            "ButtonTextForeground",
            typeof(Color),
            typeof(FrameworkElement),
            new FrameworkPropertyMetadata(Color.FromArgb(255, 0, 0, 0), FrameworkPropertyMetadataOptions.AffectsRender, OnButtonTextForegroundChanged));

        public static void OnButtonTextForegroundChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue is Color)
            {
                var brush = new SolidColorBrush(((Color) e.NewValue)) as Brush;
                if (brush != null)
                {
                    SetTextBlockForegroundColor(o as FrameworkElement, brush);
                }
            }
        }

        public static void SetButtonTextForeground(FrameworkElement fe, Color color)
        {
            var brush = new SolidColorBrush(color);
            SetTextBlockForegroundColor(fe, brush);
        }

        public static void SetTextBlockForegroundColor(FrameworkElement fe, Brush brush)
        {
            if (fe == null)
            {
                return;
            }

            if (fe is TextBlock)
            {
                ((TextBlock)fe).Foreground = brush;
            }

            var children = VisualTreeHelper.GetChildrenCount(fe);
            if (children > 0)
            {
                for (int i = 0; i < children; i++)
                {
                    var child = VisualTreeHelper.GetChild(fe, i) as FrameworkElement;
                    if (child != null)
                    {
                        SetTextBlockForegroundColor(child, brush);
                    }
                }
            }
            else if (fe is ContentPresenter)
            {
                SetTextBlockForegroundColor(((ContentPresenter)fe).Content as FrameworkElement, brush);
            }
        }
    }

and I modified the template like so:

<ContentPresenter x:Name="contentPresenter" 
                  ContentTemplate="{TemplateBinding ContentTemplate}" 
                  local:ButtonAttachedProperties.ButtonTextForeground="{StaticResource ButtonTextNormalColor}" />

WPF Sentinel objects and how to check for an internal type

6 votes

As some of you have discovered, a new feature (?) appeared WPF 4, where the data binding engine may pass your custom control instances of the class MS.Internal.NamedObject with the name "{DisconnectedItem}" into the DataContext - instead of the data item your code is expecting (this happens when a templated control is disconnected by its ItemsControl). These are called sentinel objects.

In existing code, this can lead to spurious exceptions where the code is unprepared for it. These can be swallowed up by the data binding subsystem, or they can wreak havoc. Keep an eye on your debug console.

Anyway, I learned about this on this MSDN forum. And there's a post by Sam Bent which explains it all. Go read it now, you'll want to know this. The essence is that these events should never have fired (that's the bug), so:

Ignore the DataContextChanged event if the DataContext is a sentinel object.

So, so I want to check my DataContext. But how? Consider:

public bool IsSentinelObject(object dataContext)
{
    return (dataContext is MS.Internal.NamedObject);
}

Guess what happens? It doesn't compile because MS.Internal.NamedObject is internal, and not accessible to me. Of course, I can hack it like this:

public bool IsSentinelObject(object dataContext)
{
    return dataContext.GetType().FullName == "MS.Internal.NamedObject"
           || dataContext.ToString() == "{DisconnectedObject}";
}

(or something, which works). I have also followed Sam's suggestion to cache the object for later reference equality checks (it's a singleton).

Of course, this means I don't have a problem, not really. But I'm curious, and this posting will be sure to benefit some users, so it's worth asking anyway:

Is there a way I can exactly check the type against the internal NamedObject type, without resorting to string comparisons?

This one?

var disconnectedItem = typeof(System.Windows.Data.BindingExpressionBase)
    .GetField("DisconnectedItem", BindingFlags.Static | BindingFlags.NonPublic)
    .GetValue(null);

Is there anyway of consolidating similar data bindings and/or triggers in XAML?

6 votes

I have a user control that hosts other controls. The way I implemented this is via data templates that define the control that should be associated with a specific view-model. These view-models have similar properties and interaction triggers. Please see XAML snippet below.

The problem with this approach is that I would have to copy-paste the data bindings if I want to support a new view-model. Is there any way of consolidating all similar data bindings and/or triggers into one template? I don't want to type/copy-paste the same data binding definitions into each control. (Yes, I know, I'm that lazy.)

<UserControl.Resources>   
    <DataTemplate DataType="{x:Type vm:SomeViewModel1}">
        <TextBlock Canvas.Left="{Binding Left}"
                   Canvas.Top="{Binding Top}"
                   RenderTransform="{Binding Transform}"
                   Height="{Binding Height}"
                   Width="{Binding Width}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <cmd:EventToCommand Command="{Binding MouseEnterCommand}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="MouseLeave">
                    <cmd:EventToCommand Command="{Binding MouseLeaveCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </TextBlock>
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:SomeViewModel2}">
        <Rectangle Canvas.Left="{Binding Left}"
                   Canvas.Top="{Binding Top}"
                   RenderTransform="{Binding Transform}"
                   Height="{Binding Height}"
                   Width="{Binding Width}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <cmd:EventToCommand Command="{Binding MouseEnterCommand}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="MouseLeave">
                    <cmd:EventToCommand Command="{Binding MouseLeaveCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:SomeViewModel3}">
        <Button Canvas.Left="{Binding Left}"
                Canvas.Top="{Binding Top}"
                RenderTransform="{Binding Transform}"
                Height="{Binding Height}"
                Width="{Binding Width}">
            <i:Interaction.Triggers>
                <i:EventTrigger EventName="MouseEnter">
                    <cmd:EventToCommand Command="{Binding MouseEnterCommand}"/>
                </i:EventTrigger>
                <i:EventTrigger EventName="MouseLeave">
                    <cmd:EventToCommand Command="{Binding MouseLeaveCommand}"/>
                </i:EventTrigger>
            </i:Interaction.Triggers>
        </Button>
    </DataTemplate>

    <DataTemplate DataType="{x:Type vm:SomeViewModel4}">
       <!-- Do not want copy-paste code here... -->
    </DataTemplate>
</UserControl.Resources>

You can use common style, and put both your properties and triggers (which are also properties) inside this style, look at this StackOverflow question for more details.

Is there a good pattern for exposing a generic collection as readonly?

6 votes

So I've got these classes that expose a collection of child objects.

I don't want other classes adding or removing objects from collections because I need to wire into events in the child objects, so as they get added or removed I want to be able to do additional processing. But I really love the ease of manipulating generics internally.

Did I mention this is a WPF app so I need INotifySupport?

The best I can come up with is something like this.

public class foo : INotifyPropertyChanged
{
    protected List<ChildFoo> _Children = new List<ChildFoo>();

    public foo()
    {
    }

    public void AddChild(ChildFoo newChild)
    {
        DoAttachLogic(newChild);
        _Children.Add(newChild);
        NotifyPropertyChange("Children");
    }

    public void RemoveChild(ChildFoo oldChild)
    {
        DoRemoveLogic(oldChild);
        _Children.Remove(oldChild);
        NotifyPropertyChange("Children");
    }

    public ChildFoo[] Children
    {
        get
        {
            return _Children.ToArray();
        }
    }

}

Are there serious flaws with this design that I'm not seeing?

Every time the Children property is accessed we get the overhead of converting list to an array.

Any advice on this would be great.

You should use ObservableCollection as field in your class, you then have full access to modify collection. Then expose this as ReadonlyObservableCollection via property. And if you dont change collection itself (eg. nochildren = new ObservableCollection(), you should make field readonly), then you dont need any kind of notifyPropertyChanged on this property, because it doesnt change and collection itself handles those events for its children.

public class Child
{
    public int Value { get; set; }
}

class MyClassWithReadonlyCollection
{
    private readonly ObservableCollection<Child> _children = new ObservableCollection<Child>();

    public MyClassWithReadonlyCollection()
    {
        _children.Add(new Child());
    }

    //No need to NotifyPropertyChange, because property doesnt change and collection handles this internaly
    public ReadOnlyObservableCollection<Child> Children { get { return new ReadOnlyObservableCollection<Child>(_children); } }
}

WPF: Border on three sides

6 votes

Is there way to create a border that is only on the top, left, and right sides?

Yes:

<Border BorderThickness="1 1 1 0" BorderBrush="Black"/>

Same as goes for Margin, Padding etc.

Implement faster graphics operation on WPF Canvas

6 votes

I am trying to build a simple graphics application in WPF C#. The purpose is to draw 10000*10000 rectangles of size 4 pixels each.

I have modified the OnRender method of the canvas to draw the rectangles. Drawings are performed for smaller number of rectangles (say 50*50 or 100*100 rectangles of 4 pixel each) but it is slowing down as I am increasing the no. of rectangles.

Following is my code:

  protected override void OnRender(DrawingContext dc)
    {
        base.OnRender(dc);

        FillCells(dc);

        if (_ShowGrids)
        {
            DrawGrid(dc); // draw grid lines
        }
    }
 void FillCells(DrawingContext dc)
    {

        int cellSize=4;

        for (int i = 0; i < MaxRow; i++)
        {
            for (int j = 0; j < MaxColumn; j++)
            {
                dc.DrawRectangle(GetRectBrush(i,j), GetRectPen(i,j), new Rect(j * cellSize , i * cellSize , cellSize - 1, cellSize - 1));

            }
        }
    }

The above code takes more than a minute to draw 1000*1000 rectangles.

Is there any method to make this process faster? Is there any other thing I can use in place of this?

Thanks.

The purpose is to draw 10000*10000 rectangles of size 4 pixels each.

Do NOT draw them. That simple. This would be 40k to 40k pixels.

Most will not be visible. So they must not bee drawn. Basically only draw those that are visible in the canvas. When resizing or scrolling you repaint anyway, then do the same - only draw those that are visible.

Virtualization is the key to performance here. Take things out of the drawing loop as early as possible. Stuff not visible per definition does not need to be drawn at all.

Next alternative would be not to use a canvas. Try a bitmap. Prepare it on a separate thread, then draw this one at once.

C# WPF grab screenshot with SnippingTool effect

6 votes

I'm trying to integrate a screenshot grabbing feature in my WPF app and I'd like it to look like snipping tool.

So far I've managed accomplish something similar by creating a fullscreen window (with a canvas) with opacity set to 0.5 and dark background. When I click somewhere and start dragging, a white rectangle is drawn, generating an effect similar to this.

What I'd like to have is the inner part of that rectangle opening a opacity hole in the background canvas, so that I could see through the selected area - just like snipping tool.

Problem is, being fairly new to .NET, I have no idea how or where to start. Did some research and tests on the OpacityMask field of the screenshot window but got nowhere.

Here's a little vid to show the current effect.

Edit: Also, as bonus question, is there an easy way to grab a screenshot that spans across multiple monitors (virtual screen)? Graphics.CopyFromScreen() only seems to work for 1 screen.
Already fixed this and seems to work for all possible weird virtual desktop layouts:

// Capture screenie (rectangle is the area previously selected
double left = Canvas.GetLeft(this.rectangle);
double top = Canvas.GetTop(this.rectangle);

// Calculate left/top offset regarding to primary screen (where the app runs)
var virtualDisplay = System.Windows.Forms.SystemInformation.VirtualScreen;
var primaryScreen = System.Windows.Forms.Screen.PrimaryScreen.Bounds;
if (virtualDisplay.Left < primaryScreen.Left)
{
    left -= Math.Abs(virtualDisplay.Left - primaryScreen.Left);
}
if (virtualDisplay.Top < primaryScreen.Top)
{
    top -= Math.Abs(virtualDisplay.Top - primaryScreen.Top);
}

You can have a CombinedGeometry with GeometryCombineMode="Exclude" creating a "punched" effect. Sample:

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" AllowsTransparency="True" 
    WindowStyle="None" Background="Transparent">
    <Canvas >
        <Path Stroke="Black" Fill="White" Opacity=".5">
            <Path.Data>
                <CombinedGeometry GeometryCombineMode="Exclude">
                    <CombinedGeometry.Geometry1>
                        <RectangleGeometry Rect="0,0,800,600" >
                        </RectangleGeometry>
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <RectangleGeometry  Rect="50,50,100,100" >
                        </RectangleGeometry>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
    </Canvas>
</Window>

How to Snoop proof your wpf application?

5 votes

Snoop allows you to look inside the application and change element properties. Its a great asset for developers, but can be a security issue in some cases, like when we have users who like to look in places where they shouldn't be looking. Is there a way to do something to block applications like Snoop from "snooping" your application?

And if there is no way to block it, what do you recommend to do to minimize security risks?

Snoop is a utility that allows you browse visual tree of a wpf application and view and change properties. Its very useful when you are trying to debug something and have no idea what is going on. You can find more here.

Thank you.

By implementing security properly. If your "security" can be thwarted with a tool like Snoop, then you're doing it wrong.

Suppose there's a command that only certain users can execute. It sounds like the only place you're enforcing this is at the UI level (by disabling the corresponding button, for example). That being the case, you're right - I could easily use Snoop to enable the button and execute the command. But you should be enforcing the security constraints on your server, or perhaps in your command execution logic if you have no server. Basically, security should be implemented as close to the thing you're trying to protect as possible. Security at the UI level is merely for convenience of the user.

HTH,
Kent

WPF UI not updated upon property change

5 votes

I'm not sure what I'm doing wrong here...

I have a custom HashTable that has a method that allows someone to delete a "partNumber" (a value) from the HashTable.

The delete method is as follows:

class COSC202HashTable : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    //....
    private List<int> underlyingList;
    //... 
    public List<int> HashList { get { return underlyingList; } }

    public void Delete(int partNumber)
    {
            string theAlgoritnm = Algorithm;
            if (String.Compare(theAlgoritnm, "Modulo Division") == 0 && String.Compare(Probe, "Linear Collision Resolution") == 0)
            {
                LinearModularDivision(partNumber, false);
            }
            if (String.Compare(theAlgoritnm, "Modulo Division") == 0 && String.Compare(Probe, "Key Offset Collision Resolution") == 0)
            {
                KeyOffsetModularDivision(partNumber, false);
            }
            if (String.Compare(theAlgoritnm, "Pseudorandom") == 0)
            {
                Pseudorandom(partNumber, false);
            }
            if (String.Compare(theAlgoritnm, "Rotation") == 0)
            {
                Rotation(partNumber, false);
            }

            NotifyPropertyChanged("HashList");
    }
   //.......
    private void NotifyPropertyChanged(String info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }
}

I am binding the HashTable's underlying values to the UI; however the UI is not being updated after a value is deleted. I made sure that there are no problems with spelling etc...

This is the markup I have for my WPF UI:

<Window.Resources>
    <COSC202:COSC202HashTable x:Name="TheHashTable"  x:Key="TheHashTable" PropertyChanged="TheHashTable_PropertyChanged"></COSC202:COSC202HashTable>
</Window.Resources>
<ListView x:Name="HashResults" Height="32" Width="1200" Margin="10"  HorizontalAlignment="Right"
                      DataContext="{Binding Source={StaticResource TheHashTable}}" ItemsSource="{Binding Path=HashList}" HorizontalContentAlignment="Left">
    <ListView.Background>
        <LinearGradientBrush StartPoint="0,0" EndPoint="0,2">
            <GradientStop Color="#FF000000" Offset="0"></GradientStop>
            <GradientStop Color="DarkBlue" Offset="1"></GradientStop>
        </LinearGradientBrush>
    </ListView.Background>
    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel Orientation="Horizontal"></StackPanel>
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>

    <ListView.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal" >
                <TextBlock Text="{Binding Path=.}" FontSize="11" Foreground="Azure" VerticalAlignment="Top" ></TextBlock>
                <Label Content="|" VerticalAlignment="Top" FontSize="5"></Label>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

This is the code that I am calling to delete the item in the HashTable upon button click:

  private void DeleteItem_Click(object sender, RoutedEventArgs e)
        {
            Object item = HashResults.SelectedItem;
            COSC202HashTable theHashTable = (COSC202HashTable)this.Resources["TheHashTable"];
            if (theHashTable != null && item != null)
            {
                theHashTable.Delete((int)item);
            }
            HashResults.SelectedIndex = -1;

        }

What am I doing wrong?

Thanks,

-Frinny

The first place to look for binding errors in the Output window, this quite often will point you in the right direction.

If you are binding to a custom collection then you may need to implement INotifyCollectionChanged. Or consider changing your data source to ObservableCollection, or in your case you may need ObservableDictionary.

Also you mentioned spelling errors, there are a couple of ways to ensure that this is not an issue, check out MVVM Foundation's base ObservableObject

Your code is missing a few details, such as your declaration for the StaticResource TheHashTable.

Edit: Raising PropertyChanged against a List class will not notify of changes within that List, if you need the UI to see changes within the list change the list type to ObservableCollection or create a new property:

public ObservableCollection Hash
{
  get
  {
    return new ObservableCollection(this.HashList);
  }
}

and bind to the Hash property.

Is there a good tool for debugging XAML's databinding behavior / errors at runtime?

5 votes

WPF is a great toolset, and XAML databinding is very powerful, but I've often run into difficulty arising from its transparency: It can be tough to debug a databinding failure when no errors are thrown.

For example, I recently had to change a Style declaration like this:

<DataGrid.RowStyle>
    <Style>
        <Style.Triggers>
            <DataTrigger Binding="{Binding TestProperty}" Value="False">
                <Setter Property="DataGridRow.Background" Value="Red"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

Into this:

<DataGrid.RowStyle>
    <Style>
        <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.TestProperty}" Value="False">
                <Setter Property="DataGridRow.Background" Value="Red"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>

In order for the DataGridRow property to be affected. It would be incredibly helpful to see, at design- or run-time, what the implications of binding to different sources and RelativeSources would be.

Do any such tools / techniques exist?

You can use PresentationTraceSources.TraceLevel attached property on bindings to get detailed logging in the output during runtime. See http://msdn.microsoft.com/en-us/library/system.diagnostics.presentationtracesources.tracelevel.aspx

In your case, it will look like this:

<DataGrid.RowStyle>
    <Style>
        <Style.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.TestProperty, PresentationTraceSources.TraceLevel=High}" Value="False">
                <Setter Property="DataGridRow.Background" Value="Red"/>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</DataGrid.RowStyle>