I am building a Silverlight 4 application. This application is going to print the contents of an ItemsControl. This ItemsControl uses an ItemTemplate to render the items bound to the control. In all, I have 500 items that are bound to the control.
Oddly, when I attempt to print the ItemsControl, it seems to cut off after a certain point. I cannot tell when it gets cut off. I just know that it gets cut off. I have a hunch it has something to do with virtualization. However, I'm not sure how to overcome this. Currently, I'm printing the ItemsControl like such:
private void printHyperlink_Click(object sender, RoutedEventArgs e)
{
PrintDocument printDocument = new PrintDocument();
printDocument.BeginPrint +=
new EventHandler<BeginPrintEventArgs>(printDocument_BeginPrint);
printDocument.PrintPage +=
new EventHandler<PrintPageEventArgs>(printDocument_PrintPage);
printDocument.EndPrint +=
new EventHandler<EndPrintEventArgs>(printDocument_EndPrint);
printDocument.Print("My Items");
}
void printDocument_BeginPrint(object sender, BeginPrintEventArgs e)
{}
void printDocument_PrintPage(object sender, PrintPageEventArgs e)
{ e.PageVisual = myItemsControl; }
void printDocument_EndPrint(object sender, EndPrintEventArgs e)
{}
What am I doing wrong? How do I ensure that all of the items in my ItemsControl are printed as they are rendered?
The printing APIs don't automatically paginate items in an ItemsControl for you. Furthermore, if you are printing something that is already in the visual tree, the result may get clipped to match what is being rendered in the window at the time of printing.
To print multiple pages, you'll need to:
Measure to figure out how many items to show on the page
Create visuals that only show the items you want on that page
Pass them into your "e.PageVisual"
Set e.HasMorePages to be true until you're on the last page
All in all, this can be a fair amount of work. If you're just trying to print an ItemsControl with an ItemTemplate, you'll have to do all of the work above. For slightly more sophisticated scenarios (e.g. adding page numbers, headers/footers, etc.), there's even more work to do.
That said, it's possible to build a library over the simple Silverlight printing APIs that does something like this. I recently blogged a control meant to address exactly this scenario (as well as some of the more sophisticated ones).
http://www.davidpoll.com/2010/04/16/making-printing-easier-in-silverlight-4/
Related
Im trying to add a UserControl in WPF to a grid, but it doesnt show up when im trying to add via MyGrid.Children.Add(UserControl). So i tried to display the number of childs of my grid and it says 1 after adding the usercontrol. (MyGrid.Children.Clear() doesn't work too. After clearing the grid it says that there are 0 childs left but there are still some UiElements when im compiling my program.)
This problem appears only in 1 function. In an other function (the same class) i can easily add childs to the same grid (myGrid).
My code:
private void AddDateOnClick(object sender, MouseButtonEventArgs e)
{
MyGrid.Children.Clear();
UserControlAddDate ucad = new UserControlAddDate();
MyGrid.Children.Add(ucad);
MessageBox.Show(MyGrid.Children.Count.ToString()); //Only to test if there are some childs
}
When i try to clear this grid in a other function (same class) it clears the grid. Only clearing in this function is a problem. Im not understanding why???
What is this UserControlAddDate? Maybe it is not initialized. That’s why it’s not getting added to the grid..
In that place try to add a textbox to the grid and check if it’s working. If it’s working then it’s the problem with your code.
As mentioned by Ed Plunkett, please try to use templates and databinding. It's the best way to work with WPF.
Try this anyway.
private void AddDateOnClick(object sender, MouseButtonEventArgs e)
{
MyGrid.Children.Clear();
TextBox ucad = new TextBox();
ucad.Text = “TEST”;
MyGrid.Children.Add(ucad);
MessageBox.Show(MyGrid.Children.Count.ToString()); //Only to test if there are some childs
}
I have a grid which contains 2 rows and 2 columns. Each cell hosting a windows forms host control. I want to capture the selected cell when the user clicks on any of the cell. I have searched and found that there is no 'Click' event but I can make use of 'MouseDown' event with similar results. But now I am stuck because you would think there must be an easier way like 'GetCurentRow' and 'GetCurrentColumn' to capture the selected cell but there isn't.
What I want further to do is to get the child element in that particular cell of the grid.
I tried the following code but no matter which cell I click, I always get 0 for the row and column:
void InnerGridToContainWindowsFormsHost_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
Is there any way to get the selected/clicked cell details or the children inside the cell?
I think that you may be going down the wrong route here... if you just want to know what control was clicked on, try using the VisualTreeHelper.HitTest method. You can find information on the VisualTreeHelper.HitTest Method page on MSDN. Basically, you would do something like:
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Get the position of the mouse pointer relative to the grid
Point clickPoint = e.GetPosition(grid);
HitTestResult result = VisualTreeHelper.HitTest(grid, clickPoint);
if (result != null)
{
// Do something here
}
}
You may also need some kind of basic recursive function to drill down through the Visual objects until you get to the one you want.
Not finding a move event or redraw event in the FrameworkElement class. And Google not helping either. So...
I have a custom ItemsControl populated by an observable collection in the VM. The ItemsControl itself leverages the
<i:Interaction.Behaviors>
<ei:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
behavior so the user can drag around the whole assembly.
When the user moves the assembly, I want to be notified by each item as the item is repositioned as a result of the assembly moving. So far I have tried registering for
this.myItem.LayoutUpdated += this.OnSomethingNeedsToUpdate;
but it doesn't seem to fire as I drag the assembly around.
Also
this.myItem.MouseMove += this.OnSomethingNeedsToUpdate;
only works if I mouse into the item which is not good enough. Because I am moving the ItemsControl and then have to go mouse into the item to get the event to fire.
Any ideas? Can I look to some ancestor in the visual tree for help in the form of a OneOfMyDecendantsWasRedrawn event or similar? Again I am trying to be notified when an item moves not be notified when the assembly moves.
I would say your best bet would be to add the MouseDragElementBehavior to your custom ItemsControl in code instead of in the Xaml. Here is how this might look (using a Grid since that is easier to demo):
public class DraggableGrid : Grid
{
public DraggableGrid()
{
Loaded += new RoutedEventHandler(DraggableGrid_Loaded);
}
void DraggableGrid_Loaded(object sender, RoutedEventArgs e)
{
MouseDragElementBehavior dragable = new MouseDragElementBehavior();
Interaction.GetBehaviors(this).Add(dragable);
dragable.Dragging += new MouseEventHandler(dragable_Dragging);
}
void dragable_Dragging(object sender, MouseEventArgs e)
{
// Custom Code Here
}
}
In the section that says Custom Code Here you would loop through you Items and notify them that they are being dragged.
I ended up writting another behavior for the individual items I care about and then wrote a LINQ query to search up the visual tree looking for ancestors with the MouseDragElementBehavior attached to them. That query found the ItemsControl since it was an eventual parent of the Item. I was then able to register for the Dragging event as desried.
Thanks again to Bryant for providing the solution over here.
I'm fairly new to Silverlight but experienced in web development, and I'm finding myself highly annoyed with Silverlight's default combobox. It seems to be lacking any concept of use for regular data entry. Primarily I'm wishing it would function like an HTML select box, where you can hit the drop down, then type a letter and it takes you down to the first item with that letter. Is there an easy way I'm missing to make it function like this, or a third party control that can do this?
Thanks!
You could write an attached behavior to provide this functionality. The problem is that the items in a ComboBox in Silverlight aren't always strings. They may be entire controls that the user has templated as the ItemTemplate. If you know yours are going to be string you can implement a Behavior<ComboBox> to attach to the KeyDown event and select the correct one.
public class HTMLSelectBehavior : Behavior<ComboBox>
{
protected override void OnAttached()
{
AssociatedObject.KeyDown += OnKeyDown;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
SelectedItem = AssociatedObject.ItemsSource
.FirstOrDefault(i => i.ToString().BeginsWith((char)e.Key));
}
}
This is off the top of my head so it may not be exactly right and definitely lacks many safety checks, but it should give you an idea.
I'm using a DataGrid in my silverlight application to display some data that's refreshed on a timer. My problem is that when this happens the vertical scrollbar in the grid resets to the top, whereas I want it to stay in the same position. Does anyone know how I can make this happen?
I've tried overriding the ItemsSource property on the grid to store the vertical scroll position and then reset it, but this only affects the scrollbar and doesn't force the correct rows to be displayed. Is there a way to force this behaviour?
Here is a similar question about Setting the scroll bar position on a ListBox
After rebinding Silverlight Listbox control how do you get it listbox to scroll to back to the top?
Since the DataGrid also supports a ScrollIntoView method, you should be able to use a similar technique such as
theDataGrid.ItemsSource = data;
theDataGrid.UpdateLayout();
theDataGrid.ScrollIntoView(theDataGrid.SelectedItem, theDataGrid.Columns[0]);
I couldn't find a decent answer last time I looked. I wanted to keep the current element selected in the grid but that wouldn't work on an ICollectionView refresh (I use MVVM and get automatic updates from the server).
ScrollIntoView() was not an option for me because the currently selected item may NOT be in view. Having the CurrentChanged event firing out of control was also quite a bother.
In the end, I used the Infragistics grid and it does just that out of the box. Problem solved for me.
You may have a look at the DevExpress free grid. I think it had the same nice behaviour (I tested it but I can't remember the outcome).
You could try setting the SelectedItem thro the UI thread, so that the UI can refresh itself,
like so
private void Button_Click(object sender, RoutedEventArgs e)
{
Person p = new Person() { Name="sss",Age=11}; //datagird's itemsSource is Collection<person>
people.Add(p);
dg.SelectedItem = p; //dg is my datagrid name
Dispatcher.BeginInvoke(() => { dg.SelectedItem = p; });
}
Im assuming that new rows are loaded thro the ViewModel, so thats why it makes sense to place the BeginInvoke there. Since the ViewModel operations run on a different thread, and just setting the SelectedItem on its own might not work, this has worked for someone else
I've also had issues with this. I solved it by remembering the item I want to scroll to, then re-binding the DataGrid. I handle the LayoutUpdated event in order to implement the desired functionality:
void MyDataGrid_LayoutUpdated(object sender, EventArgs e)
{
// Reference the data item in the list you want to scroll to.
object dataItem = yourDataItem;
// Make sure the item is not null and didn't already scroll to the item.
if (dataItem != null && this.dataItemScrolledTo != dataItem)
{
// Remember the item scrolled to.
this.dataItemScrolledTo = dataItem;
// Scroll datagrid to the desired item.
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
}
}
I've modified CodeMaster's solution so that you don't need a class level variable. Put this code in the method that updates the ItemsSource. It will dynamically create the eventhandler, attach it, then detach it.
EventHandler MyDataGrid_LayoutUpdated = null;
MyDataGrid_LayoutUpdated = (s, e) =>
{
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
MyDataGrid.LayoutUpdated -= MyDataGrid_LayoutUpdated;
};
MyDataGrid.LayoutUpdated += MyDataGrid_LayoutUpdated;