How to show a loading graphic/animation when wpf data binding is taking place - wpf

I have a WPF user control that contains a DataGrid. I'm binding an ObservableCollection of view models to it. Each view model has another collection of view models that I'm using to bind another DataGrid to. So the effect is a DataGrid with a nested DataGrid contained in the row details template.
Normally the binding is quite quick, but sometimes when there's a lot of data it can hang the UI while the binding/drawing is taking place.
Is there a way where I can either show a loading animation or progress bar while the binding/drawing is in progress?

There's probably a more formal, or at least simpler solution, but you could use a modal popup window that is shown in a worker thread and is closed asynchronously when your is grid done loading:
Window waitWindow = new Window { Height = 100, Width = 200, WindowStartupLocation = WindowStartupLocation.CenterScreen, WindowStyle = WindowStyle.None };
waitWindow.Content = new TextBlock { Text = "Please Wait", FontSize = 30, FontWeight = FontWeights.Bold, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += delegate
{
Dispatcher.BeginInvoke(new Action(delegate { waitWindow.ShowDialog(); }));
DataLoader dataLoader = new DataLoader(); // I made this class up
dataLoader.DataLoaded += delegate
{
Dispatcher.BeginInvoke(new Action(delegate() { waitWindow.Close(); }));
};
dataLoader.LoadData();
};
worker.RunWorkerAsync();
You can replace the TextBlock with something pretty like a loading bar, and you could make the code re-usable by parameterizing the object that handles the loading of the grid(s) and passing it in to a commonly used method.
I hope that works for you.

I had the same problem and this is how I solved it.
I discovered that DataGrid will only start creating controls when it displays the grid. In my case this was the time consuming process. After some tracing I found that creating the controls happens during measuring !
My solution is to override MeasureOverride and put the wait cursor around the base class call. I encapsulated my wait cursor setting in a class. So the code looks like this.
protected override Size MeasureOverride(Size availableSize)
{
using (new DisposableWaitCursor(this))
{
return base.MeasureOverride(availableSize);
}
}

Related

Is it possible to create a control once and have it generated everytime it is needed?

Say for example you have a stackpanel that you would like to programmatically add buttons to.
The code behind for the button generation and addition to the stackpanel is:
Button button = new Button();
button.Content = "Button";
button.HorizontalAlignment = HorizontalAlignment.Left;
button.Name = "Button" + i
stackPanel1.Children.Add(button);
My question is - Is it possible to generate the button once and have it as some kind of template that can be added to the stackpanel whenever it is needed without going through the generation code again?
In WPF each UIElement can only be the logical child of one control at any given time, see WPF Error: Specified element is already the logical child of another element. Disconnect it first, so no you can't use the same button and add it to another control later on, unless you're sure you've gotten rid of that stackpanel
But, you can do recycling. See Optimizing Performance: Controls. especially if you are willing to override MeasureOverride and ArrangeOverride of you stackpanel
I've actually written such a recycler because I had grids with many controls, and I wanted to implement some kind of virtualizing grid. These are the main methods of my class:
internal class Recycler
{
private UIElementCollection _children;
public Recycler(UIElementCollection children)
{
_children = children;
//You need this because you're not going to remove any
//UIElement from your control without calling this class
}
public void Recycle(UIElement uie)
{
//Keep this element for recycling, remove it from Children
}
public UIElement GiveMeAnElement(Type type)
{
//Return one of the elements you kept of this type, or
//if none remain create one using the default constructor
}
}

WPF Loading multiple tabs simultaneously in background with freezing UI

Basic UI
I have UI shown in above image.
Each Tab have Some controls like grid which has some data filled in from datatable.
What i want?
behaviour just like web browsers when we open new tab, it gets loaded in background.
Entire browser doesn't freeze. you are able to see and interact with facebook if twitter is loading in another tab.
How it is behaving currently ?
When i click on one of the item in left hand side treeview , tab gets opened but it freezes entire UI of the app, i am not able to click on another treeview item until the tab has completed its data into grid.
Current Implementation
on treeview click event, another thread is created which is calling addTab()
function.
treeviewlist_SelectedItemChanged()
{
Dim thread As New Thread(Sub() Me.AddTab( itemno))
thread.Start()
}
below function creates backgroundWorker thread object bw,
bg_dowork() gets datatable from DB to bind to the grid in tabitem.
bg_RunWorkerCompleted() binds that dt to grid in tabitem.
Addtab()
{
Thread.Sleep(TimeSpan.FromSeconds(2))
Dispatcher.BeginInvoke(New Action(Function()
Dim bg As New BackgroundWorker
AddHandler bg.DoWork, Sub(sender, e)
//get dt
End Sub
AddHandler bg.RunWorkerCompleted, Sub(sender, e)
// bind dt to grid
End Sub
Return 1
End Function), DispatcherPriority.Send)
}
To be honest i am new to WPF and vb.net and somehow feel i have messed up somewhere badly.
This is c# code. But it explains the concept nicely. It uses async/await keywords. The for-loop below can be replaced with your data-access code.
ViewModel vm = new ViewModel();
...
private async void Button_Click(object sender, RoutedEventArgs e)
{
await _loadEmployees();
}
private Task _loadEmployees()
{
Task t = Task.Factory.StartNew(() =>
{
var employees = new ObservableCollection<Employee>();
for (long i = 0; i < 999999; ++i)
employees.Add(new Employee() { Name = "Name" + i, Address = DateTime.Now.ToString() });
vm.Employees = employees;
});
return t;
}
DataGrid is bound to Employees property of ViewModel .
Set Width and Height of DataGrid so that DataGrid can use UIVirtualization.
You can ask more doubts, I will update here.

dynamic tooltips on Winforms controls

I am looking for the cleanest way to bind the same datasource to a control's tooltip that I am binding to the control itself. For example, I have the line
control.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
where dataFeatures is of type BindingSource. I repeat similar lines for many controls on a WinForm Form. Some of these controls can adopt values whose text can span a greater text width than what is visible within the control itself. Instead of redesigning the layout of the form to account for the possibility of partially hidden text in some controls in a few situations, I would like to have the tooltip of each control be bound to the same property of the BindingSource as the controls' EditValue or Text property. Is this possible? I can imagine there is a way to do it by hand by handling the EditValueChanged event like I already do for different reasons, but I was hoping there would be a cleaner solution than having to add new lines of code for each control.
Anybody have a suggestion?
Thanks!
0. For DevExpress controls you can just bind DevExpressControl.ToolTip property to the same value:
devExpressControl.DataBindings.Add(new Binding("EditValue", dataFeatures, "Key", true));
devExpressControl.DataBindings.Add(new Binding("ToolTip", dataFeatures, "Key", true, DataSourceUpdateMode.Never));
1. For standard WinForms controls you can use System.Windows.Forms.ToolTip component and its ToolTip.Popup event. For each control set its ToolTip to some value otherwise ToolTip will never appears:
control.DataBindings.Add(new Binding("Text", dataFeatures, "Key", true));
toolTip1.SetToolTip(control, "Some value");
Now you can use ToolTip.Popup event:
private bool _updatingToolTip;
private void toolTip1_Popup(object sender, PopupEventArgs e)
{
if (_updatingToolTip) return;
//Get binding for Text property.
var binding = e.AssociatedControl.DataBindings["Text"];
if (binding == null) return;
//Get binding value.
var manager = binding.BindingManagerBase;
var itemProperty = manager.GetItemProperties().Find(binding.BindingMemberInfo.BindingField, true);
object value = itemProperty.GetValue(manager.Current);
string toolTipText;
if (value == null || string.IsNullOrEmpty(toolTipText = value.ToString()))
{
e.Cancel = true;
return;
}
//Update ToolTip text.
_updatingToolTip = true;
toolTip1.SetToolTip(e.AssociatedControl, toolTipText);
_updatingToolTip = false;
}
You can easily implement dynamic tooltips with the ToolTipController component. Put this component onto the Form, and assign to each editor via the BaseControl.ToolTipController property.
When it is done, you can handle the ToolTipController.BeforeShow event and change the text according to the control state. The active control is passed through the SelectedControl property of the event parameter.

Dialog implementation fails, is already the logical child of another element

I am trying to implement dialogue window in WPF + PRISM + MVVM application. for now I managed to create sample service and each module is able to use this service to show any view in window, but the problem is something very unusual and can not make it work.
Here is the contract of the window service.
public interface IUiDialogueService : IDisposable
{
void Show<TView>(TView view) where TView : IViewModel;
}
public class UiDialogueService : IUiDialogueService, IDisposable
{
private Window _dialogueWindow;
#region Implementation of IUiDialogueService
public void Show<TView>(TView view) where TView : IViewModel
{
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
_dialogueWindow.ShowDialog();
_dialogueWindow = null;
}
}
Here is how I access my window service from module.
private void OnStartWizard()
{
_dialogueService.Show(ServiceLocator.Current
.GetInstance<IOrgManagementOrganizatioSetupViewViewModel>());
}
everything works well when I first open window but after I close it and open same or other view inside window I revive following exception
Specified element is already the logical child of another element. Disconnect it first.
this exception is thrown by following code.
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
Could anyone explain what is going on wrong here and Is there any better way to get child(dialogue) window in MVVM architectur?
Try adding the following code before the last line of Show:
_dialogueWindow.Content = null;
Show should now look like this:
public void Show<TView>(TView view) where TView : IViewModel
{
_dialogueWindow = new Window
{
SizeToContent = SizeToContent.WidthAndHeight,
ResizeMode = ResizeMode.NoResize,
ShowInTaskbar = false,
Content = view.View
};
_dialogueWindow.ShowDialog();
_dialogueWindow.Content = null;
_dialogueWindow = null;
}
Each element in WPF can only belong to one parent element. Even if an element is not shown (or shown anymore), the parent-child relationship remains. If you want to give an element a new parent you need to remove it first from the old parent.
In your case, in Show() you are setting the Content of the window to your view. Even after that window was shown, the view still is the child of that window. If you now try to show that same view in a different window, you'll get that exception.
The best way is to remove the view from the Window (described in Daniel Hilgarth's answer). Alternatively, you could check if the view already has a Parent, and manually remove it from that parent first.

Wpf Adorner not responding to interactions

I'm trying to create an overlay in wpf (with darkening background), similar to the ones you can find on the web to popup images.
I would like it to be reusable in more than 1 part of the application, with diffent types of content.
this is the temporary code of the constructor of the adorner class (just to try)
private readonly Grid _grid = new Grid();
public DarkOverlayAdorner(UIElement adornedElement, Object content) :
base(adornedElement)
{
_grid.Background = new SolidColorBrush(Color.FromArgb(99, 0, 0, 0));
IsHitTestVisible = true;
var visual = content as UIElement;
if (visual != null)
_grid.Children.Add(visual);
}
In addition in the class (of course), I have the ovverrides of MeasureOverride and ArrangeOverride to give the adorner the correct size of the adorned element, GetVisualChild, and VisualChildCount...
The problem here is that the adorner is correctly shown, but no events or behaviour are applied on the adorned element. For example:
AdornerLayer layer = AdornerLayer.GetAdornerLayer(textBoxProva);
layer.Add(new DarkOverlayAdorner(textBoxProva, new Button{Content = "prova"}));
The button here is shown, but I can-t click the button and no effects on button mouseover are applied.
I still can't figure out the problem.
Ok, I've lost a lot of time trying to figure out what was the problem.
In the end I found the solution:
If you want the element added to react to events, I think that the element must be bound to the visual tree of the adorner.
The way to do it is to use a VisualCollection, intitialized to the adorner itself:
VisualCollection visualChildren;
FrameworkElement #object;
public DarkOverlayAdorner(UIElement adornedElement) :
base(adornedElement)
{
visualChildren = new VisualCollection(this);
#object = new Button {Content = "prova"};
visualChildren.Add(#object);
}
protected override Visual GetVisualChild(int index)
{
return visualChildren[index];
}
This way the events are correctly routed.
You might want to take a look at the ChildWindow control in the Extended WPF Toolkit. It is a control that pops up a Window with a modal background effect, and you can specify the content to put inside the Window.

Resources