wpf rendering synchronization - c#

I have two custom WPF controls (with custom rendering logic) placed on top of each other (transparent background and stuff). At some point i want to re-draw those controls. My first idea was to call:
foreach(var control in Controls)
{
control.InvalidateVisual();
}
But that did not quite work. I mean it does force the rendering of my controls, but visual update does not happen simultaneously. The controls update one by one, which does not look nice and can lead to confusion (in cases where one control displays an updated visual, while the other still displays an old one). The documentation on InvalidateVisual method is really poor, but i guess it is an async method, which is why i am having this issue.
So the question is: is there a way to synchronize rendering of multiple controls? Apart from creating one mega control containing all the rendering logic (i would like to avoid that).

You generally shouldn't use InvalidateVisual().
To update a control which is staying the same size, you can create a DrawingGroup and put that into the visual-tree during OnRender() -- then you can update the drawing group anytime you like using DrawingGroup.Open(), and WPF will update the UI.
For your case, this would look something like:
class Control1 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class Control2 : UIElement {
DrawingGroup backingStore = new DrawingGroup();
protected override void OnRender(DrawingContext drawingContext) {
base.OnRender(drawingContext);
Render(); // put content into our backingStore
drawingContext.DrawDrawing(backingStore);
}
private void Render() {
var drawingContext = backingStore.Open();
Render(drawingContext);
drawingContext.Close();
}
private void Render(DrawingContext) {
// put your existing drawing commands here.
}
}
class SomeOtherClass {
public void SomeOtherMethod() {
control1.Render();
control2.Render();
}
}

You need to suspend refreshing control until you finish invalidating all of them one way is like this :
class DrawingControl
{
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);
private const int WM_SETREDRAW = 11;
public static void SuspendDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
}
public static void ResumeDrawing( Control parent )
{
SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
parent.Refresh();
}
}
see here for other ways : How do I suspend painting for a control and its children?

Another approach is to use data-binding to trigger the rendering if control has some Data property that is bound to viewmodel.
public static readonly DependencyProperty DataProperty
= DependencyProperty.Register("Data", typeof(DataType), typeof(MyView),
new FrameworkPropertyMetadata(default(DataType),
FrameworkPropertyMetadataOptions.AffectsRender));
public DataType Data
{
get { return (DataType)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
Note the AffectsRender flag. Then you can re-draw all controls simultaneously by simultaneously updating bound properties:
foreach(var viewModel in ViewModels)
{
viewModel.Data = ...;
}

Related

How to update ContentControl UI when custom property change on design time?

I have make a ContentControl and it has some custom Propertities. The control itself works fine but I like to update its interface during design time in XAML editor. The problem is next: The control's UI update if I change its Size (SizeChanged event will do that) but I cannot find any way to do this if CustomProperty like OffsetX changes during design time.
So, how to change the following code to make this happen? It isn't too convenient to update Control UI changing its size every time.
public sealed class MyControlElement: ContentControl
{
//
//SOME INITIALIZE CODE IS HERE
//
public MyControlElement() => DefaultStyleKey = typeof(MyControlElement);
protected override void OnApplyTemplate()
{
//
//SOME INITIALIZE CODE IS HERE
//
base.OnApplyTemplate();
}
//OFFSET X DESCRIPTION
[Description("OffsetX"), Category("MyControlElementParameters"), Browsable(true)]
//OFFSET X
public int OffsetX
{
get
{
return (int)GetValue(OffsetXProperty);
}
set
{
if (OffsetX != value)
{
SetValue(OffsetXProperty, value);
OnOffsetXChanged(this, new EventArgs());
}
}
}
public static readonly DependencyProperty OffsetXProperty = DependencyProperty.Register("OffsetX", typeof(int), typeof(MyControlElement), PropertyMetadata.Create(0));
public event EventHandler OffsetXChanged;
private void OnOffsetXChanged(object sender, EventArgs e)
{
UpdateControlUI();
this.OffsetXChanged?.Invoke(this, e);
}
}
I found some kind of "Hack". Still hoping to find better solution. The next trick works and it is possible to update Control interface during design time.
First need to add handler for Loaded.
public MyControlElement()
{
this.DefaultStyleKey = typeof(MyControlElement);
this.Loaded += MyControlElement_Loaded;
}
private void MyControlElement_Loaded(object sender, RoutedEventArgs e)
{
//
//SOME INITIALIZE CODE HERE IF NEEDED
//
//RUN CONTROL VISUAL UPDATER ONLY IF IN DESIGN MODE
if (DesignMode.DesignModeEnabled) ControlDesignTimeUIUpdater();
//FLAG - CONTROL HAS BEEN INITIALIZED
IsControlInitialized = true;
}
And lets add ControlDesignTimeUIUpdater void for UI update. This void has a loop to keep UI updated during design time.
private async void ControlDesignTimeUIUpdater()
{
double OldImageWidth = ImageWidth;
double OldImageHeight = ImageHeight;
CornerRadius OldImageCornerRadius = ImageCornerRadius;
double OldBorderThickness = BorderThickness;
ImageSource OldMyImageSource = MyImageSource;
while (this.IsLoaded)
{
//CHECK CHANGES DELAY 100ms
await Task.Delay(100);
//MAKE SURE CONTROL IS INITIALIZED BEFORE ANY UI UPDATES
if (IsControlInitialized)
{
if (OldImageWidth != ImageWidth)
{
OldImageWidth = ImageWidth;
SetImageWidth();
}
if (OldImageHeight != ImageHeight)
{
OldImageHeight = ImageHeight;
SetImageHeight();
}
if (OldImageCornerRadius != ImageCornerRadius)
{
OldImageCornerRadius = ImageCornerRadius;
SetImageCornerRadius();
}
if (OldBorderThickness != BorderThickness)
{
OldBorderThickness = BorderThickness;
SetBorderThickness();
}
if (OldMyImageSource != MyImageSource)
{
OldMyImageSource = MyImageSource;
SetMyImageSource();
}
//
// ETC.
//
}
}
}
By this Hack it is possible update control in "real-time" during design. It's even possible add animations, size changes etc.

How to auto open a ComboBox when focused?

I have a Form which contains several ComboBoxes.
I want one ComboBox of them to open the elements list when it gets the focus, both from keyboard and mouse.
The DroppedDown property of the ComboBox class manages the visibility of the elements list.
The event that most fits my needs is Enter, so the code I wrote is:
private void comboBox1_Enter(object sender, EventArgs e)
{
this.comboBox1.DroppedDown = true;
}
It works, but when directly clicking on the icon located on the right part of the ComboBox which does NOT have the focus, the elements list opens up and the suddenly disappears after its opening.
I've tried many ways to fix this weird behavior, checking the Focused property or using other events like DropDown or MouseClick, without getting any acceptable result.
A simple way (which doesn't force you to override a ComboBox derived Control's WndProc) is to simulate a HitTest, testing whether the MouseDown occurred on the ComboBox button area; then, set DroppedDown = true; only if it didn't.
Thus, when the Mouse is clicked on the Button, you won't cause a double effect, moving the Focus in an unexpected way (for the Control).
GetComboBoxInfo() is used to retrieve the correct bounds of the ComboBox Button, whether the current layout is (LTR or RTL).
private void comboBox1_Enter(object sender, EventArgs e)
{
var combo = sender as ComboBox;
if (!combo.DroppedDown) {
var buttonRect = GetComboBoxButtonInternal(combo.Handle);
if (!buttonRect.Contains(combo.PointToClient(Cursor.Position))) {
combo.DroppedDown = true;
Cursor = Cursors.Default;
}
}
}
Declarations for the GetComboBoxInfo() function:
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
internal static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);
[StructLayout(LayoutKind.Sequential)]
internal struct COMBOBOXINFO {
public int cbSize;
public Rectangle rcItem;
public Rectangle rcButton;
public int buttonState;
public IntPtr hwndCombo;
public IntPtr hwndEdit;
public IntPtr hwndList;
}
internal static Rectangle GetComboBoxButtonInternal(IntPtr cboHandle) {
var cbInfo = new COMBOBOXINFO();
cbInfo.cbSize = Marshal.SizeOf<COMBOBOXINFO>();
GetComboBoxInfo(cboHandle, ref cbInfo);
return cbInfo.rcButton;
}
Create a new class the is inherited from ComboBox:
public class Combo : ComboBox
{
protected override void OnClick(EventArgs e)
{
if (!DroppedDown) base.OnClick(e);
}
}
And in its click call base.OnClick(e); if its not dropped down.
use this one instead of combobox. (Basically click event will be ignored if dropped down)
Use the following code on your form constructor:
this.comboBox1.GotFocus += (sender,args) => comboBox1.DroppedDown = true;

Grids constructor isn't called on add to ListView

I am developing a Windows phone app, and I have this logic:
I have a chat view, with ListView which contain video or text messages. My video messages are custom controls which implements my IPlayableMessage interface:
public class VideoMessage : Grid , IPlayableMessage
{
public VideoMessage()
{
this.Loaded += Loaded;
}
private void Loaded(object sender, RoutedEventArgs e)
{
MessagePlayer.Sub(this);
}
public void Play()
{
//Some logic for video play
}
public Player MessagePlayer
{
get { return (Player)GetValue(MessagePlayerProperty); }
set { SetValue(MessagePlayerProperty, value); }
}
public static readonly DependencyProperty MessagePlayerProperty =
DependencyProperty.Register("MessagePlayer", typeof(Player), typeof(VideoMessage), null);
}
This is my interface:
public interface IPlayableMessage
{
void Play(Uri uri = null);
}
In the constructor I am calling Sub method with this parameter, which enabls my in the future to play my video message.
This works great, however if I add a new message to my ListView (and I see it on the view) the controls constructor isn't called and by that my object is not passing it self to the Player.
Why the constructor and Loaded events are not called when new element added to the ListView?
The constructor called thought, as I scroll up and down the ListView and it performs its paging....
After a while I've moved my registration to:
protected override Size ArrangeOverride(Size finalSize)
It reduced the numbers of unregistered grids but it's called several times per object and it's also does not cover a 100 percent of the items.
Any help will be appreciated. :)
Did you try to add callback to the MessagePlayer property and handle operation there?
public static readonly DependencyProperty MessagePlayerProperty = DependencyProperty.Register(
"MessagePlayer", typeof (Player), typeof (VideoMessage), new PropertyMetadata(default(Player), PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as VideoMessage;
(e.NewValue as Player).Sub(control);
}

How to override the onTouchUp in the user control which can be used to fire the event not only on this user control, but whole application

I am a new to the C# and WPF. Right now, I want to override onTouchUp event in my user control. Then I can add user control in the references for reuse. The problem is each time I want to test this event on the application, the event only fires at area of user control, not whole screen. Does anyone have a solution for this?
Basically, you need to attach to PreviewTouchUp event on a element, that covers the interactive area. It may be application wndows as well. Handling mouse or touch events outside window is not recommended.
Here's an example, how can you attach any behaviour to any element directly in xaml:
<Window my:MyBehaviour.DoSomethingWhenTouched="true" x:Class="MyProject.MainWindow">
public static class MyBehaviour
{
public static readonly DependencyProperty DoSomethingWhenTouchedProperty = DependencyProperty.RegisterAttached("DoSomethingWhenTouched", typeof(bool), typeof(MyBehaviour),
new FrameworkPropertyMetadata( DoSomethingWhenTouched_PropertyChanged));
private static void DoSomethingWhenTouched_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uiElement = (UIElement)d;
//unsibscribe firt to avoid multiple subscription
uiElement.PreviewTouchUp -=uiElement_PreviewTouchUp;
if ((bool)e.NewValue){
uiElement.PreviewTouchUp +=uiElement_PreviewTouchUp;
}
}
static void uiElement_PreviewTouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
//you logic goes here
}
//methods required by wpf conventions
public static bool GetDoSomethingWhenTouched(UIElement obj)
{
return (bool)obj.GetValue(DoSomethingWhenTouchedProperty);
}
public static void SetDoSomethingWhenTouched(UIElement obj, bool value)
{
obj.SetValue(DoSomethingWhenTouchedProperty, value);
}
}

How to limit item position into Canvas?

[EDIT]
Alright,
I edited this post since the code I posted back then had no real links with what I'm trying to do now, but the question is the same.
When I'm talking about limiting objects to a Canvas it was more like a Mouse Clipping, but as I read on many threads, this feature doesn't exist in SL. So I searched a bit around all forums and got this link. But I wasn't able to reproduce it all. Here is the code that is used for the Drag&Drops Events:
public class RoomImage : ContentControl
{
public RoomImage()
{
DefaultStyleKey = typeof(RoomImage);
}
public static readonly DependencyProperty BackgroundImageProperty = DependencyProperty.Register("Source", typeof(ImageSource), typeof(RoomImage), null);
public ImageSource BackgroundImage
{
get { return (ImageSource)GetValue(BackgroundImageProperty); }
set { SetValue(BackgroundImageProperty, value); }
}
//Instance Drag variable
private FrameworkElement _translateZone;
bool _isDrag;
Point StartingDragPoint;
public double Top
{
get { return (double)GetValue(Canvas.TopProperty); }
set { SetValue(Canvas.TopProperty, value); }
}
public double Left
{
get { return (double)GetValue(Canvas.LeftProperty); }
set { SetValue(Canvas.LeftProperty, value); }
}
//Instance Drag events
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_translateZone = GetTemplateChild("PART_TranslateZone") as FrameworkElement;
DefineDragEvents();
}
private void DefineDragEvents()
{
if (_translateZone != null)
{
_translateZone.MouseLeftButtonDown += new MouseButtonEventHandler(translateZone_MouseLeftButtonDown);
_translateZone.MouseLeftButtonUp += new MouseButtonEventHandler(translateZone_MouseLeftButtonUp);
_translateZone.MouseMove += new MouseEventHandler(translateZone_MouseMove);
}
}
private void translateZone_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDrag = true;
//start the drag
FrameworkElement DragBar = (FrameworkElement)sender;
DragBar.CaptureMouse();
// drag starting point
StartingDragPoint = e.GetPosition(this);
}
private void translateZone_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
FrameworkElement translateZone = (FrameworkElement)sender;
translateZone.ReleaseMouseCapture();
_isDrag = false;
}
private void translateZone_MouseMove(object sender, MouseEventArgs e)
{
if (_isDrag)
{
UIElement ui = (UIElement)this.Parent;
Point Point = e.GetPosition(ui);
Move(Point.X - StartingDragPoint.X, Point.Y - StartingDragPoint.Y);
}
}
public void Move(double left, double top)
{
Left = left;
Top = top;
}
}
I found this part of code in a tutorial where they didn't explain the Mouse.Clip at all. I can understand it and reuse it, but I have no clue where I could set the limits. The Parent of this item is a Canvas by the way.
If anyone can provide me some sort of code, or where I should implement mine it would be great!
Thank you, Ephismen.
A Canvas has no size as far as its children are concerned. It is just a relative starting point for rendering. A fixed canvas size is only relevant to the parent of the Canvas.
If you mean the objects are being drawn outside the canvas rectangle, then that is the correct behaviour for a canvas.
To stop objects being drawn outside a canvas you need to set a clipping rectangle in the Clip property of the Canvas.
Update:
Here is a very nice example here of how to have a ClipToBounds attached property. That is definitely the easiest way to implement bounds clipping I have seen.
Another update:
So you want to just keep the child items within the parent canvas. If your items vary in size & shape that is basically collision testing with the sides and cap the min/max left/top values. How complex are the shapes you are dropping? Rectangles are obviously very easy to calculate (as are circles).

Categories

Resources