I'm currently writing a WPF progress bar that includes a rate (see Windows 8 - Fancy Progress Bars API?).
The screenshot below shows what I've got so far (left) and a badly done all in code as part of my learning exercise (right). I'm now trying to convert the code version to use as much XAML as possible.
I've got most of the way there by creating a new class called RateBase and implementing it in a similar way to RangeBase. I've then added a new instance and provided a template file RangeGraph. I'm attempting to do this as by the book as possible, but I'm not sure how to tackle the final stage.
I now wish to add a graph, this graph is to display the rate as it has changed historically as the progress has progressed. I have 'Rate' as a value I can bind to, but I believe somwhere I need a Double[] containing my historical rate values. My question is where should this be placed (I don't really want to pollute RateBase) and how do I bind to it from my template (I don't believe I can bind to RangeGraph.cs if I add properties on there or am I wrong?)
You are right, you will need historical Data. In my opinion, whenever the bound Dependency Property Rate changes, you should move the old value into an IEnumerable that's defined on the graph control itself (The same place that has the DP) and use that to draw the lines. I personally would create a class named MyControlData and add an instance of that to the control.
You might also want to add a Timer and move the current Rate to the IEnumerable when it Elapses, so longer streaks of the same rate will appear as multiple bars. Depends on how you actually determine progress. You might get into the following dilemma here : The Rate changes at a different interval than the Percentage in most cases - what floats your boat?
Keeping the history in your control's scope leaves your application agnostic to the history of your Rate, but lets your control display it as required.
To use DataBinding in a UserControl, edit the <UserControl x:Name="myControl"> node in Control.xaml and add a name like shown here. Wherever you want to bind, refer to ElementName=myControl. Please note that you will have to implement INotifyPropertyChanged on the Control (or on MyControlData) if you want to achieve this - or, and that would be advisable, directly implement it as a dependency property.
And BTW, if you have no idea how to achieve what you intend to have a look into ItemsControl. I think what you want to do can easily be achieved by means as simple as using ItemsControl and ItemsTemplate, where the ItemsSource is your historical data and the ItemTemplate depicts your current rate in comparison to your MaxRate. MaxRate is another property you can set from the DependencyProperty Rate's changed handler.
Related
I'm using the Telerik WPF RadGanttView control to display a bunch of data. Since the data can be arbitrarily weird - events that are instantaneous, or that last days, or have a week between them - it's not possible to set a PixelLength (i.e. a scale factor) that's guaranteed to be useful. I've been asked to make it possible to change this scale factor with a slider. Unfortunately, just changing the scale factor with the slider has a usability issue, which I need to fix by manually adjusting where the view is scrolled. I'm at a loss as to how exactly.
To outline the usability problem I'm fixing: the view works by providing a "viewport", located at an "offset", over the whole "extent" of the data set. (Terms lifted from the Telerik API. These seem to simply represent pixels in the canvas underlying the scrollable view.) When the scale factor goes from, say, 100% to 200% (which corresponds to halving the PixelLength), what happens is that the width of the extent is doubled, but the horizontal offset remains the same. The consequence is that after zooming in, it's quite likely you'll see entirely different data than before, since the events that were there before got "pulled out" of the viewport to the right.
The way I intend to fix this is: grab the offset/viewport/extent etc before zooming in, zoom, then do some maths I haven't figured out yet with that and the new offset/viewport/extent. The problem is: the properties of RadGanttView that describe the scrolling stuff are not DependencyPropertys, and I cannot simply bind them to properties on my ViewModel. (In fact, they're not even accessible in XAML to begin with, RadGanttView implements IScrollingInfo explicitly.)
So, my question is: how do I, in my ViewModel, or wherever else in reaction to the ViewModel's scale factor changing, access properties of a control in the corresponding View, that cannot be data-bound? Every search I tried tells me that accessing the view from the viewmodel is "not MVVM", but since Telerik is a third-party library, I can't really refactor how this works on their side.
A fill-in-the-blanks outline of my code:
FooViewModel.cs
class FooViewModel
{
// A slider in the view pushes its value into this property
double ScaleFactor
{
get { /*...*/ }
set
{
PixelLength = GetNewPixelLength(value);
// ...
}
}
// The RadGanttView pulls its scale from this property
double PixelLength
{
get { ... }
set
{
// How do I get these values?
var oldOffset = ???;
var oldExtent - ???;
// Trigger the actual change in the view.
PropertyChanged("PixelLength", ...);
var newExtent = ???;
???.HorizontalOffset = GetNewOffset(...);
}
}
}
FooView.xaml
<UserControl ... d:DataContext="{d:DesignInstance my:FooViewModel}">
<telerik:RadGanttView x:Name="Gantt">
<!-- ... -->
</telerik:RadGanttView>
</UserControl>
Things I've looked into:
Making a bunch of DependencyProperty wrappers in the FooView code-behind that just access corresponding properties in the RadGanttView. This seems like it both is a horrible abuse of the system - i.e. it doesn't seem to make sense to have a dependency property not backed by a DependencyObject. And also that it plain wouldn't work - in WPF, the view seems to "push" data into the view model, and I'd still have no way to actually get the current values, since the values of the wrapper properties would never get updated.
Uh, Commands, maybe? I'm fairly new at WPF, I have no clue how those work at all, merely a vague impression that they might be a loosely coupled way for the view model to talk to the view.
Attached properties? Custom bindings? Way above my pay grade, if they help I don't know how myself. It seems like they could accomplish the "dirty" solution of just binding a control to a view model property. Since the type of that property would be IScrollingInfo, not the whole view, I could live with that.
Attached Behaviors may solve your issue. They are basically Attached Properties with a callback.
Check out my answer here. Just instead of KeyDown event, you register to the Changed event (or whatever your control is actually calling it) and then assign the value you get from the Changed event to your Attached Property, and you have two way binding on a non-bindable property
I guess I'm a little confused as to whether properties like display range should be placed in the model (that gets inherited as a datacontext so that subcontrols can bind to it easily)or whether I should have properties be placed in the graphviewer class, and then let the components that need access to it have their own properties that they bind to the ancestor instead. Is it cleaner to bind to an ancestor control or just to bind off the model? I feel like the latter is cleaner, but then display range is pretty clearly a property of the view.
For example. I have a property AxisdivisionUnit that is needed in a scrollviewer, as well as used by a few thumbs to recalculate position on graph updates. The scrollviewer only appears when a treeview in the top level control (graphviewer) is populated. So I could either put the property axisdivisionunit on the graphviewer and bind the property to properties in the scrollviewer and thumb. Or I could have the thumb and scrollviewer bind to properties in the model (viewmodel if i were better at separating the UI out entirely.
Let me see if I can help..
First off, since you are discussing mainly the presentation of what things look like on your UI, then I do not think that the property should be in your model at all. The real question is whether it belongs in your View or ViewModel.
AxisDivisionUnit, sounds like it is only part of how the graph looks. I'm thinking that it would make more sense for that to be in the view only. If you had some properties describing the limits of your graph that were tied to business logic, then something like that may be better off in the ViewModel since you could possibly want to test that code and if you were to replace the UI you'd still want to enforce those exact same limitations.
I guess ask yourself, "If I were to replace this graph with a totally different graph and UI to display the same data, would I have to enforce this same logic?" If the answer is no, that it is just how you want to display it for this case... then it belongs in the View and you can bind a Control's property to another control's property or use triggers, behaviors, etc. to implement it in the View.
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I'd like to create a WPF application and would like some advice on the most appropriate approach.
I want to create an RSS reader that automatically refreshes when a new RSS entry is added. The problem is that I don't want to use traditional controls (listbox/listview) to display the data. I'd like the feed items to appear in panels randomly on the screen. These panels consist of several textblocks. Each panel displays one feed item.
It would look something like this:
Concept
This raises several questions:
1: Generate panels completely from code, or use a Custom Control?
I would model a class like a panel as described above. This class manually adds all controls to the form and drops the panel at a random location on the form. When a new RSS entry is added, an instance of this class gets instantiated and passes the rss information as parameters.
On the other hand, it might be better to create an UserControl for this. Is it easy to create this UserControl by code and pass it the parameters in the constructor?
2: Can my data/panel automatically update when a new RSS entry has been added online?
Right now I would refresh everything each (x) seconds and check against a collection of panels if there has to be created a new one. If so, create a new panel and drop it randomly on the form.
Is there a better way of doing this? I can use a local ObservableCollection with databinding that automatically updates a control (listbox, etc) when the collection changes, can this also be done with an online source like an RSS feed?
The most ideal way would be that my application gets notified when a new RSS entry has been added, downloads the last entry and creates a new Panel (trough code or trough a UserControl)
If this is hard thing to accomplish, I'll use the traditional refresh method.
3: Do I have to use DependencyObject/DependencyProperty?
I know DependencyObject & DependencyProperty expose some powerful functionality for UserControls, but I don't really know how to use them. Are they necessary for this kind of application?
4: Do I have to use WCF (Windows Communication Foundation)?
I'm not really experienced with "advanced" WPF stuff like advanced databindings, DependencyObjects and UserControls, but I love to learn!
I would recommend firstly looking into using the MVVM design pattern, and using an MVVM framework. Secondly, you could achieve this effect using an ItemsControl and use a Canvas as it's ItemsPanel type, then you could use a custom ItemTemplate which renders each data object using a UserControl.
The user control would have a dependency property which is the data item, and you would bind this in the item template declaration.
You could have a model which models each RSS entry (RSSEntry) and perhaps an RSSEntryViewModel which adds the x and y coordinates on the canvas.
Your screen view model would then have an ObservableCollection of RSSViewModel which you would add/delete etc to and the UI would automatically update.
You wouldn't need a service layer if you didnt want to, but as long as your view model retrieves the entries via an abstraction, it should be easy to refactor in the future.
Generate panels completely from code, or use a Custom Control? I usually try to do as much as I can in XAML declaratively, separating logic and presentation usually helps scalability of the application and code quality - but of course there are limits. UserControls generally are not supposed to have parameters in their constructors (not that they can't have them, but you have to have a parameterless constructor so the class can be instantiated from XAML).
Can my data/panel automatically update when a new RSS entry has been added online? There has to be something to send update notifications to the WPF layer, so it can update the display. In case of a RSS application, I guess you will have to manually periodically scan the RSS channels for updates (RSS is a pull technology) and in case of update add the item into the ObservableCollection which will send the appropriate update notification for you.
Do I have to use DependencyObject/DependencyProperty? No, you can use INotifyPropertyChanged. DependencyProperties are generally used in properties which will serve as binding target (the property that is declaring the binding) or in properties that will take advantage of any other DP feature - value inheritance or animation. INotifyPropertyChanged is enough for the properties that are bound to (that are named in the binding expression). Note that you can use NotifyPropertyWeaver to generate the notifications for INotifyPropertyChanged automatically - you just create the OnPropetyChanged method and the weaver will then call it whenever any property of the object is changed! And it even integrates beautifully with Visual Studio.
Do I have to use WCF (Windows Communication Foundation)? For WCF you have to have something to communicate with - it is a communication framework after all. Do you?
You should use a WPF listview (or similar; not sure which control exactly), and theme it to match your desired "panel" idea. That is one of the great strengths of WPF. Then you get all the benefits of the built-in control, with any look you want.
Bind to the ObservableCollection; how you update that observable collection is your business. I don't think RSS has a "push notifications" part of its spec, so polling is how these things are usually done. But in the end it doesn't really matter; that part of your code is completely separate from WPF, so as long as it updates the ObservableCollection, you're good.
Either DependencyObject/DependencyProperty or INotifyPropertyChanged are generally necessary for any kind of WPF application with databinding. It's worth learning them, and then maybe learning a framework that abstracts them away for you.
No; WCF has nothing to do with WPF. You can use any technology to talk to the server that you like.
1: Generate panels completely from code, or use a Custom Control?
Create two view model classes. One class will model the view of all your items, and one representing the content of a single item. The former will contain an observable collection of the latter.
Build a user control to display each.
The container view will be an ItemsControl whose ItemsSource is bound to its collection of item view models, whose ItemsPanel is a Canvas, and whose ItemContainerStyle binds Canvas.Top and Canvas.Left properties to Top and Left properties in the item view models. When a new item is added to the view model's collection, binding will automatically create a new panel for it.
The item view models will generate the random values of Top and Left themselves. (You could also have them request the values from the container when they're constructed.)
(If the term "view model" doesn't mean anything to you, you need to research the model/view/view model pattern, aka MVVM.)
2: Can my data/panel automatically update when a new RSS entry has been added online?
First off, you need to research how RSS aggregators work, since you're writing one. That will explain to you the mechanics of getting updates from RSS feeds. That problem is completely distinct from the problem of presenting the updates once you get them.
Your RSS aggregation layer will check feeds, look for new items, and when it finds new items, raise an event. Your UI layer will handle events raised by the aggregation layer and create new view model objects for every new item received.
This use of events completely decouples the two components from each other. For instance, you can test your UI by building a mock aggregator that generates test messages and having your UI listen to it instead of your real aggregator. Similarly, you can test your aggregator without building the - you can just build a listener that registers for its events and dumps items to the console.
3: Do I have to use DependencyObject/DependencyProperty?
You probably won't don't have to implement your own, no.
4: Do I have to use WCF (Windows Communication Foundation)?
Why wouldn't you?
I'm a bit confused as to what a viewmodel's role is beyond databinding. I have a menu built in silverlight. The menu has x number of menu items which is determined at runtime. One of the features I would like to add to this is that each menuitem has a different text colour when hovered over.
Is it the role of the view to have a colour selector method or should the view handle this in it's code behind?
Normally I would keep the coloring/styling in XAML if possible - My view of the ViewModel is that it is responsible for providing all the data (ie. not graphical stuff) from the Model in a manner the View can consume.
If it was complex logic that determined the color and it was to be reused - I might be tempted to put it in the ViewModel tho.
The view model is used by the data binding process as a "safe" way to allow you to sort/filter/group the records as seen by a specific control without (necessarily) making changes to the actual bound data set (that is, unless/until you tell it to). (FMI read Bea's article here.)
I agree with Goblin here, in that the presentation aspects like color might be best kept separate in the XAML, for example in the DataTemplate used by that control.
I have a MVVM (Prism) application that I need to implement a master details screen wheer the master is a listview and the details is displayed next to it. Read-only seems easy enough (haven't done it yet but I've got my head around WPF binding) but edit/add confuses me.
How to I make it so the master is not updated until the details is saved?
How do I make it so you can't change the master's current selection while in edit/add mode?
I've been googling a plenty but have not found any meaty examples of this.
Thanks.
PS: This view is a child view on a larger screen. This is why I want both master and detail together.
You certainly can do this, though in my opinion such a UI design fails to harness the full power of WPF. Old WinForms UIs usually didn't update most of the application until data was saved to SQL Server (or wherever) because they didn't have real business objects and a powerful binding system like WPF. Trying to copy WinForms limitations within WPF seems like a step backward to me. Why not show the latest data everywhere it is visible in the UI, including in the master view? Also, why not allow the user to edit multiple items before saving, for example marking any edited but unsaved item with an animated marker in the master view? Combine these with a generalized undo and you have a better design and more intuitive for the user.
However if your business requirements make it absolutely necessary, here is how to do it:
Preventing changes to data from being visible outside the detail until it is saved
Upon entry into your "edit/add mode", make a copy of the data objects and set your detail view's DataContext to the copy instead of the live object. When the data is "saved", copy the data from the shadow copy back into the live object and set your detail view's DataContext back where it should be.
Preventing the master's current selection from changing while in edit/add mode
Two possibilities:
During edit/add mode, change the master view to disallow mouse hit testing or keyboard focus
When edit/add mode begins, capture the "current selection" then add an event handler that watches for "current selection" changes and immediately changes the selection back to what it was. When edit/add mode ends, remove the handler. This handler can be conveniently coded using a lambda expression and using a closure on a local variable to store the current selection.
Thanks for the answer. Now I've re-read my message, I see it is rather vague. I have a screen that edits an object which contains multiple lists of other child objects. I've implemented these as different tabs in a tab control. One of these tabs edits the comments, so I wanted to display a list of comments with an edit panel for the current selection next to the list. The user could then use add, edit or delete buttons to update the list. I wanted to do this in a pure(ish) MVVM way.
I came up with the following design which seems to work with minimal hacks.
The View includes a list of the child objects simply as a ListView bound to an observable collection within the ViewModel. I included a child object buffer – this is used to buffer changes until they are ready to be saved back to the list (or thrown away).
The View also includes an edit panel bound to the buffer object in the ViewModel. The buffer is updated whenever the list view’s current selection changes using a deep copy. I tried using data binding on the Selecteditem property but the set was never called, so a small code-behind method was added to force the property to be updated when the selection was changed.
The list view and edit view are mutually exclusive. In theory you could hide the disabled one, perhaps using a flip screen. As a general pattern, it is better for my app to have both visible at the same time as the edit panel may show extra information not shown in the list view. The choice as to which panel is enabled is controlled by binding IsEnabled to a ViewModel property like IsEditCommentMode.
Commands to manage the list have to be added, these are New, Editand Delete. Note that Add and Edit will set set up the buffer then set IsEditCommentMode to true. These list management commands are only available when IsEditCommentMode is false.
The edit panel implements Save and Cancel commands, they are only be enabled when IsEditCommentMode is true. When Save is executed, it should copy from the buffer to the list (either add or update) and fire the change notification. Finally, it should set IsEditCommentMode to false.
This all works well and does not seem to violate any MVVM tenents (in my humble but often flawed opinion).