Attach event handlers to controls within a data template - c#

I have a list box in WPF which displays the status of a number of worker threads. Included in the display is a progress bar.
It turns out that progress bars have an issue (defect?): the "Maximum" value is a dependency property, but the progress bar does not update when this property changes.
Somebody encounted a similar problem in this question. The suggested solution is to add a handler to the progress bar that deals with the dependency property changing.
But my progress bar is created by the WPF framework through a data template. (See code below) How could I attach a handler to these dynamically created progress bars?
Or, is there another way to solve this problem?
<DataTemplate x:Key="DataTemplateTransferWorker">
<Border Style="{StaticResource TransferWorker}" Height="Auto">
<StackPanel Orientation="Vertical">
<TextBlock Style="{StaticResource ItemHeader}">Transfer Worker</TextBlock>
<TextBlock Style="{StaticResource FieldValue}" Text="{Binding StatusDescription}" />
<!-- Note that the maximum comes from a changing property. -->
<ProgressBar
Margin="6, 3, 6, 3"
Height="12"
Orientation="Horizontal"
Background="Transparent"
Maximum="{Binding ServerJob.FileSize, Mode=OneWay}"
Value="{Binding BytesUploaded, Mode=OneWay}" />
Later Edit
It turns out that it's not as simple as the Maximum binding value being broken, because there are working examples where progress bars dynamically updates perfectly well.
It wasn't an INotifyPropertyChanged issue. I was notifying of any changes to the ServerJob property, (but not the ServerJob.FileSize property which is constant for each server job.) I also put a breakpoint on ServerJob.FileSize.get, and the framework was indeed querying the filesize in response to the property changed notification.
I also added two TextBlocks to the control, bound to ServerJob.FileSize and BytesUploaded respectively. And they updated exactly as they should.
But for some reason, as soon as the BytesUploaded value became non-zero the progress bar would zoom to 100%.
I've worked around the problem by adding a new property "PercentageComplete" which calculates the progress, and that way I can keep the maximum at a constant value of 100.

I tried creating an application with a ProgressBar in a DataTemplate whose Maximum is bound to a changing property. Like Greg Sansom i do not encounter any problem, i suppose you did not implement INotifyPropertyChanged in the class which contains the property you bound to, doing so should be the easiest solution to the problem.

Related

How to bind an automatically-set parameter in WPF with MVVM

So here's the situation:
I have a label (custom control derviative of it, if it matters), and I need to get its width and height with MVVM. However, if I set either of the parameters to {Binding XXX}, they are no long Auto and thus when I change the font in runtime their size doesn't update.
I read about using ActualWidth/Height, which sounds like just what I need besides the fact that it's not a dependence parameter, thus it seemss like I'd need to break MVVM for it.
Are there any better solutions?
EDIT
Well, the element in XAML looks nothing special. Just a million of bindings.
<local:DraggableLabel
Content="123"
Margin="{Binding Label2Position, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontFamily="{Binding Label2Font.Family}"
FontSize="{Binding Label2Font.Size}"
FontWeight="{Binding Label2Font.Weight}"
FontStyle="{Binding Label2Font.Style}"
Foreground="{Binding Path=Label2Color,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource ColorToBrushConverter}
}"/>
The default is,
Width="Auto"
which doesn't have to be written explicits, but can (changes nothing). It makes it resize when the font changes. If I set it to
Width="Binding {Label1Width}"
The binding works fine, but I no longer get the auto-adjustment.
Okay, so I found a workaround.
Since I had create a custom control, I was able to create a new DependencyProperty called RealWidth, then I added a OnSizeChanged event that updates it with the value of ActualWidth that you CAN get from inside the element every time the size of the element changes.

WPF MVVM DataTemplates - View Not updating

I'm working on a digital signage WPF application and the basic structure is setup as follows:
I have a single view with a grid in it bound to a playlist object. The playlist contains panes, the panes contain playlist items, and the playlist items each contain a content item. I use DataTemplates to build the view for each piece, e.g.
<!-- This template represents a single content pane, no real display here, just empty space with a height and width -->
<DataTemplate DataType="{x:Type entities:ContentPane}">
<ContentControl
Content="{Binding CurrentPlaylistItem, Mode=OneWay}"
Height="{Binding Height, Mode=OneTime}"
Width="{Binding Width, Mode=OneTime}"
HorizontalAlignment="Left"
VerticalAlignment="Top"
/>
</DataTemplate>
<!-- This is a playlist item which is contained within a ContentPane.
This also has no real display, just a placeholder for the content item, and will likely contain some transition information-->
<DataTemplate DataType="{x:Type entities:PlaylistItem}">
<inf:TransitionableContentControl Content="{Binding ContentItem}"></inf:TransitionableContentControl>
</DataTemplate>
<!-- image item template
the path can be any valid uri e.g. http://... or file://... -->
<DataTemplate DataType="{x:Type contentTypes:ImageContentType}">
<Grid Background="Transparent" x:Name="ImageType">
<Image Source="{Binding Bitmap, Mode=OneTime}"></Image>
<inf:BusinessAd
ContainerHeight="{Binding ImageHeight, Mode=OneTime}"
ContainerWidth="{Binding ImageWidth, Mode=OneTime}"
Visibility="{Binding AdText, Converter={StaticResource _VisibilityConverter}, Mode=OneWay}"
Text="{Binding AdText.Text, Mode=OneTime}"
AdFontSize="{Binding AdText.TextStyle.FontSize}"
AdFontFamily="{Binding AdText.TextStyle.FontFamily}">
<ContentControl
Content="{Binding AdText, Mode=OneTime}"
ContentTemplate="{StaticResource BusinessAdTextTemplate}">
</ContentControl>
</inf:BusinessAd>
</Grid>
</DataTemplate>
As the datacontext changes, the content in each pane transitions from one item to the next. I'm running into a problem where the user is placing the same item in a pane twice in a row, be it a video, image, etc. What happens is the change goes undetected and the UI does not update. In the case of a video, it freezes on the last frame of the first video and the whole application hangs.
I have tried doing an OnPropertyChanged("ContentItem") within the PlaylistItem class, tried setting Shared="False" on my data templates, I tried changing properties on the ContentItem object and raising PropertyChanged events, nothing seems to work. I turned on tracing on the databinding to see what was happening and it all appears to be working correctly. When I change properties on the ContentItem it shows a new hash for the new item, but no change on the UI.
Within the TransitionableContentControl in the PlaylistItem, the OnContentChanged override is never hit when going from one content item to the same one. If I swap that control out with a regular ContentControl, no change.
Former Employee is correct about the diagnosis. Furthermore, unless you manage to re-assign the inner datacontexts your templates will not restart from the beginning, as you have noticed. You will have to assign your CurrentPlaylistItem to some default empty item first to reset stuff all the way through. To avoid races and nasty flicker, do it on a dispatcher priority higher than UI rendering. Try this:
// CurrentPlaylistItem = pli ; -- not good
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// clear item
// null doesn't work, because TransitionableContentControl's
// inner ContentControl would be empty and the brush created
// from it would be a null brush (no visual effect)
CurrentPlaylistItem = new PlaylistItem () ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
Application.Current.Dispatcher.Invoke (new Action (() =>
{
// set new item, possibly same as old item
// but WPF will have forgotten that because
// data bindings caused by the assignment above
// have already been queued at the same DataBind
// level and will execute before this assignment
CurrentPlaylistItem = pli ;
this.OnPropertyChanged ("CurrentPlaylistItem") ;
}), DispatcherPriority.DataBind) ;
})) ;
The issue here is the ContentControl (or rather the underlying DependencyProperty framework) doesn't fire OnContentChanged when .Equals is true for old and new Content. One workaround is to have your own dependency property and transition on CoerceValue. I'll email you the code and you can post it here.

OnScreen event in wpf

I have a small custom control which downloads & displays a contacts image. It ensures that only 1 image is being downloaded at a time by adding itself to a static queue of images awaiting to be downloaded.
It is possible to get quite long contact lists. So I want it to only enter the download queue when it actually becomes visible on the screen (there's a default image).
I've tried placing the logic in the Loaded event, overriding OnRender and the IsVisibleChanged event, but none seem to give me what I want.
any suggestions?
D.R
Edit:
This is a WPF Application, sorry for not mentioning before...
Some system controls (like ListView) have property "VirtualMode" if you set it to true and handle RetrieveVirtualItem event. This event invokes for items which are visible currently and you have to fill those items with data(images) you want. So that you don't need to fill all items at once.
Have you considered using a PriorityBinding in order to provide that functionality?
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<StackPanel Orientation="Horizontal">
<Image>
<Image.Source>
<PriorityBinding FallbackValue="{StaticResource ImgDownloading}">
<Binding Path="ImageSource" IsAsync="True" />
</PriorityBinding>
</Image.Source>
</Image>
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>

how do i force the converter to execute when the property is changed?

This is my image in XAML:
<Image Margin="0"
Stretch="UniformToFill"
Source="{Binding '', Converter={StaticResource byteArrToBitmap}}">
<ToolTipService.ToolTip>
<Border BorderBrush="#FF3D3D3D" Background="#FFFFE1E1">
<TextBlock Text="{Binding PhotoDescription, TargetNullValue=No description}"
Width="170"
Height="Auto"
FontFamily="Georgia"
TextWrapping="Wrap"
Foreground="#FF373737"/>
</Border>
</ToolTipService.ToolTip>
</Image>
This Image is inside DataTemplate of listbox. As you can see I have source set to {Binding ''} which means it is bound to datacontext and not to the actual property that I want to bind. This is essential because I have some logic being performed based on which I am returning an image.
I am downloading the images on the fly from webservices and it returns a byte[]. I have INotifyPropertyChanged implemented in the class. However, as I have binding setup to the DataContext, the converter does not reexecute itself when the byte[] is downloaded in asynchronous manner.
It's a verty bad idia to bind something to the DataContext it self.. right now Silverligth 4 do not implement INotifyPropertyChanged for DataContext, so you have two options:
1) wait for Silverligt 5:
Silverlight 5–Features list
The DataContextChanged event is being introduced. Markup extensions allow code to be run at XAML parse time for both properties and event handlers, enabling cutting-edge MVVM support.
2) create some object that implement INotifyPropertyChanged, create some property, and bind to that property...
I believe you just want
{Binding Converter={StaticResource byteArrToBitmap}}
Not
{Binding '', Converter={StaticResource byteArrToBitmap}}
As not specifying any property path will bind to the DataContext. I have no idea what {Binding ''} does, but it's not standard practice. I'm surprised it doesn't throw an exception, actually.
That said, the way I would handle this is to have a wrapper object, which has a property representing the byte array - that way you can raise INotifyPropertyChanged events in a more straightforward way. I believe there's a way to invalidate the whole object, but I don't recall what it is.
I am assuming that your data context is the byte[] that you want to convert. So you have to ensure that the PropertyChanged event is raised whenever the asynchronous download is complete. Also, ensure that the event is raised on the Main Thread and not on a Worker or Background thread.
If you change your binding to be bound to some property, even if its unnecessary in the scope of your application you can indirectly cause the Converter to reevaluate when that property changes. That property needs to exist on an object that implements INotifyPropertyChanged.
I can give source code if needed.

WPF Combobox binding: can't change selection

After wasting hours on this, following on the heels of my Last Problem, I'm starting to feel that Framework 4 is a master of subtle evil, or my PC is haunted.
I have three comboboxes and a textbox on a WPF form, and I have an out-of-the-box Subsonic 3 ActiveRecord DAL.
When I load this "edit record" form, the comboboxes fill correctly, they select the correct items, and the textbox has the correct text. I can change the TextBox text and save the record just fine, but the comboboxes CANNOT BE CHANGED. The lists drop down and highlight, but when you click on an item, the item selected stays the same.
Here's my XAML:
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Asset</TextBlock>
<ComboBox Name="cboAsset" Width="180"
DisplayMemberPath="AssetName"
SelectedValuePath="AssetID"
SelectedValue="{Binding AssetID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Status</TextBlock>
<ComboBox Name="cboStatus" Width="180"
DisplayMemberPath="JobStatusDesc" SelectedValuePath="JobStatusID"
SelectedValue="{Binding JobStatusID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Category</TextBlock>
<ComboBox Name="cboCategories" Width="180"
DisplayMemberPath="CategoryName"
SelectedValuePath="JobCategoryID"
SelectedValue="{Binding JobCategoryID}" ></ComboBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="10,10,0,0">
<TextBlock Width="80">Reason</TextBlock>
<TextBox Name="txtReason" Width="380" Text="{Binding Reason}"/>
</StackPanel>
Here are the relevant snips of my code (intJobID is passed in):
SvcMgrDAL.Job oJob;
IQueryable<SvcMgrDAL.JobCategory> oCategories = SvcMgrDAL.JobCategory.All().OrderBy(x => x.CategoryName);
IQueryable<SvcMgrDAL.Asset> oAssets = SvcMgrDAL.Asset.All().OrderBy(x => x.AssetName);
IQueryable<SvcMgrDAL.JobStatus> oStatus = SvcMgrDAL.JobStatus.All();
cboCategories.ItemsSource = oCategories;
cboStatus.ItemsSource = oStatus;
cboAsset.ItemsSource = oAssets;
this.JobID = intJobID;
oJob = SvcMgrDAL.Job.SingleOrDefault(x => x.JobID == intJobID);
this.DataContext = oJob;
Things I've tried:
Explicitly setting IsReadOnly="false" and IsSynchronizedWithCurrentItem="True"
Changing the combobox ItemSources from IQueryables to Lists.
Building my own Job object (plain vanilla entity class using INotifyPropertyChanged).
Every binding mode for the comboboxes.
ItemsSource="{Binding}"
The Subsonic DAL doesn't implement INotifyPropertyChanged, but I don't see as it'd need to for simple binding like this. I just want to be able to pick something from the dropdown and save it.
Comparing it with my last problem (link at the top of this message), I seem to have something really wierd with data sources going on. Maybe it's a Subsonic thing?
EDIT: For some reason the set accessor is hit only on the AssetID property and only the first time. WPF is now heading for WTF :)
EDIT 2: You gotta be kidding me- I've removed the binding (ie it only has a displaymemberpath, a valuememberpath and an itemssouce) and it's STILL doing it! It accepts your first selection, and then won't change.
WPF Combo Boxes will not change the selected item if the currently selected item and the item that was just selected are considered equal by the object.Equals() method called on the newly selected object (i.e newlyslected.Equals(previoslySelected) ).
Overriding the Equals method on the class your binding the combobox items, should resolve the issue your are seeing.
I've narrowed it down to the Subsonic objects used as ComboBoxItems.
If you create a new class that uses exactly the same code as the relevant parts of the Subsonic one, it works.
If you use POCOs/datatables for the combos and Subsonic for the record being edited, it works.
But if you use Subsonic for both, it doesn't.
I had hoped to extend the subsonic objects and not have to code a full-blown BLL tier. Looks like I'm faced with doing that or throwing out Subsonic for the DAL. I might post a more specific question for the Subsonic folks.
Many thanks to all who contributed.
Old topic but I had the same problem and difficulty finding solution. This might help someone else.
Clue is above in WPF not detecting a different item has been seleted by user. (Symptom - event ComboBox_SelectionChanged only fires on first selection)
My scenario - lookup combo populated from IList built from a DISTINCT query. In this case the result of using NHibernate ICriteria.SetResultTransformer which only returns SOME fields, importantly NOT including the unique entity ID.
Solution - loop thru' IList after retrieval and give each entity a unique ID. WPF sees them as individuals and behaves appropriately.
Its only a value lookup - its the value content I was after.
The 'temporary' entities are never persisted. In this case it was a better approach than messing with overriding the object's Equals method for the sake of a simple GUI issue. An alternative would be to just copy or tranform the list into a format where WPF uses the value field to determine 'difference'...
Sounds like the field is somehow readonly, or that your change isn't being persisted. After the binding sets the new value, it will re-read the property to ensure that it was actually changed. If your property returns the old value, then it'll be re-selected in the combo box, giving the appearance that the value never changed.
I don't know that DAL, but can you step through the property setter code? You might also have an issue with type conversion.
EDIT reading your comment about the red rectangle -- it sounds as though your property (or something to do with the binding) is raising an exception. Unless, of course, you're using data validation in your UI. You might turn 'Break on all exceptions' in the debugger's settings, assuming you're using Visual Studio.
EDIT 2 You should check the VS Output pane for any error messages related to binding. You can also read this blog post which gives more info on debugging bindings.
It's hard to tell from a small sample of your code but try commenting out the line:
//this.DataContext = oJob;
and see if this helps.
Setting the DataContext and ItemsSource might be causing a conflict.
Did you write any global style for your combo box which may have a bug or something missing? Or are you using pure default styles for your combobox? Try removing any default styles applied.
Are you wiring up any events? If your code hooks up for event like PreviewMouseLeftButtonUp and marks event as handled then probably combobox may ignore and wont select anything.

Categories

Resources