I have a problem with running Storyboard for dynamically created UserControl in .net WPF.
These are examples of my classes:
class EventsPage {
// ...
public void AddEvent(Event #event) {
var eventUC = new EventUserContrl(#event);
eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
EventsStackPanel.Children.Add(eventUC);
}
private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time) {
// Create frames using custom functions for creating them.
var frames = new DoubleKeyFrameCollection() {
StoryBoardsBuilder.CreateEasingDoubleKeyFrame(0.0, eventUC.ActualHeight),
StoryBoardsBuilder.CreateEasingDoubleKeyFrame(time, destinationHeight)
};
// Create Animation.
var heightSizeAnimation= StoryBoardsBuilder.BuildDoubleAnimationUsingKeyFrames(
FillBehavior.Stop, frames, eventUC.Name, new PropertyPath("Height"));
// Create StoryBoard.
var storyboard = new Storyboard();
// Add Animations into StoryBoard.
storyboard.Children.Add(heightSizeAnimation);
// Create final function.
storyboard.Completed += (sender, e) => {
eventUC.Height = destinationHeight;
};
// Run animation.
storyboard.Begin(this);
}
// ...
}
And after launching it, at storyboard.Begin(this), an exception is shown:
System.InvalidOperationException: „Name „” cannot be found in the namespace „ProjectName.Pages.EventsPage ”.”
I did something like this but for manually placed user controls in page, and it works, but this won't.
This is StoryBuilder code:
public static EasingDoubleKeyFrame CreateEasingDoubleKeyFrame(
double frameTimeInSeconds,
double value) {
// Create double key frame.
return new EasingDoubleKeyFrame() {
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(frameTimeInSeconds)),
Value = value
};
}
public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
FillBehavior fillBehavior,
DoubleKeyFrameCollection keyFrames,
string targetName,
PropertyPath targetProperty) {
// Create animation.
var animation = new DoubleAnimationUsingKeyFrames();
// Set animation end behavior.
animation.FillBehavior = fillBehavior;
// Set animation frames.
animation.KeyFrames = keyFrames;
// Set animation target object.
Storyboard.SetTargetName(animation, targetName);
// Set animation target property.
Storyboard.SetTargetProperty(animation, targetProperty);
return animation;
}
It looks like you are setting the target of the Storyboard wrong.
Storyboard.SetTargetName expects a registered element name.
You have two options: either use Storyboard.SetTarget or register the control's name and use Storyboard.SetTargetName.
Solution 1: Storyboard.SetTarget (recommended)
The recommended approach is to use Storyboard.SetTarget, when creating the animation in C# (instead of the more convenient XAML). Storyboard.SetTargetName expects an element within a XAML namescope, having the FrameworkElement.Name set using the X:Name directive. Using Storyboard.SetTarget eliminates the requirement of the target element to be registered within a name scope.
StoryBoardBuilder.cs
class StoryBoardBuilder
{
public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
FillBehavior fillBehavior,
DoubleKeyFrameCollection keyFrames,
DependencyObject target,
PropertyPath targetProperty)
{
// Create animation.
var animation = new DoubleAnimationUsingKeyFrames();
// Set animation end behavior.
animation.FillBehavior = fillBehavior;
// Set animation frames.
animation.KeyFrames = keyFrames;
// Set animation target object.
Storyboard.SetTarget(animation, target);
// Set animation target property.
Storyboard.SetTargetProperty(animation, targetProperty);
return animation;
}
}
Example
class EventsPage
{
// ...
public void AddEvent(Event #event) {
var eventUC = new EventUserContrl(#event);
eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
}
private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time)
{
...
// Create Animation.
var heightSizeAnimation= StoryBoardBuilder.BuildDoubleAnimationUsingKeyFrames(
FillBehavior.Stop,
frames,
eventUC,
new PropertyPath("Height"));
...
}
// ...
}
Solution 2: Storyboard.SetTargetName
Storyboard.SetTargetName expects a named FrameworkElement as animation target. The name of this element must be registered within the current XAML name scope. This is done automatically when naming elements in XAML using the x:Name directive.
Since you have decided to create the elements in C# you have to register the element name manually. In case there is no active name scope, you also have to create a new name scope manually.
The easiest to get access to an existing name scope is the reference of a named XAML element. On this element you call FrameworkElement.RegisterName to register an element. See Microsoft Docs: Targeting Framework Elements, Framework Content Elements, and Freezables to learn more.
StoryBoardBuilder.cs
class StoryBoardBuilder
{
public static DoubleAnimationUsingKeyFrames BuildDoubleAnimationUsingKeyFrames(
FillBehavior fillBehavior,
DoubleKeyFrameCollection keyFrames,
string targetName,
PropertyPath targetProperty)
{
// Create animation.
var animation = new DoubleAnimationUsingKeyFrames();
// Set animation end behavior.
animation.FillBehavior = fillBehavior;
// Set animation frames.
animation.KeyFrames = keyFrames;
// Set animation target object.
Storyboard.SetTargetName(animation, targetName);
// Set animation target property.
Storyboard.SetTargetProperty(animation, targetProperty);
return animation;
}
}
Example
class EventsPage
{
// ...
public void AddEvent(Event #event)
{
var eventUC = new EventUserContrl(#event);
// Name the element
eventUC.Name = "MyEventUserContrl";
// Register the element name in the current name scope manually
// using an existing named element
EventsStackPanel.RegisterName(eventUC.Name, eventUC);
EventsStackPanel.Children.Add(eventUC);
eventUC.ExpandCollapseAnimation += ExpandCollapseAnimation;
}
private ExpandCollapseAnimation(EventUserControl eventUC, double height, double time)
{
...
// Create Animation.
var heightSizeAnimation= StoryBoardBuilder.BuildDoubleAnimationUsingKeyFrames(
FillBehavior.Stop,
frames,
eventUC.Name,
new PropertyPath("Height"));
...
}
// ...
}
Related
Friends!
There is a main window, it contains a frame with which I switch between pages.
I have a page that has a canvas. Canvas in the background stream receives data in the form of images in a mosaic view.
foreach (var item in CoreData.fillWallArray.GetConsumingEnumerable())
{
if (File.Exists(item.PathFile))
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
Image image = new Image();
image.Source = BitmapImageFromFile(item.PathFile);
image.Width = (int)Math.Truncate((CoreData.settings.CellWidth * 30) / 2.54);
image.Height = (int)Math.Truncate((CoreData.settings.CellHeight * 30) / 2.54);
Canvas.SetLeft(image, item.Column * (int)Math.Truncate((CoreData.settings.CellWidth * 30) / 2.54));
Canvas.SetTop(image, item.Row * (int)Math.Truncate((CoreData.settings.CellHeight * 30) / 2.54));
can.Children.Add(image);
}));
Thread.Sleep(100);
}
}
My task is to bring this canvas to the second screen. To do this, I create a second window and, as a context, pass the canvas that I need.
var _BroadcastWindow = new BroadcastWindow();
_BroadcastWindow.DataContext = this.can;
_BroadcastWindow.Show();
And in the second window, I link the data.
<Grid>
<Grid.Background>
<VisualBrush Visual="{Binding}"/>
</Grid.Background>
</Grid>
Everything works fine, data from the canvas synchronously displayed in the second window. But as soon as I switch to another page, the Visualbrush is no longer updated. As soon as I switch back to the page with the canvas I see in the second window, it is updated.
What could be the problem?
I also tried to call Measure, Arrange, UpdateLayout when adding data to the canvas in the background thread, but this did not produce results.
I assume when you say "go to another page" you mean something along the lines of:
frame.Navigate(new System.Uri("Page2.xaml", UriKind.RelativeOrAbsolute));
Every time you do this, your app loads a new Page from a given source. If the current page happens to be the Page that has your Canvas on it, navigation will create a new Canvas instance. If not, and there is no JournalEntry.KeepAlive="true" set for the Page with your Canvas, then contents of the Frame will just get recreated from the Source file every time it is displayed, and a new Canvas will be created with it. Something will get disconnected or prematurely destroyed at some point. Even with KeepAlive set to True, you'll probably just end up with multiple instances of Canvas loaded in memory. Which one do you want to bind to...?
Some alternative approaches off the top of my head:
Cache the Image itself in your View Model and bind both your Canvas on the Page and the VisualBrush to that.
Cache the whole Canvas in your View Model, then switch its contents as needed.
The second approach required only minimal changes to your code, so I could throw in a working example (although I don't know if it's the most optimal):
In Page1.xaml (the page that displays the Canvas):
<Grid>
<ContentControl Content="{Binding canvas, Source={x:Static local:CanvasViewModel.Instance}}" />
</Grid>
In BroadcastWindow.xaml:
<Grid>
<Grid.Background>
<VisualBrush Visual="{Binding}"/>
</Grid.Background>
</Grid>
Example singleton View Model to hold the canvas:
public class CanvasViewModel
{
Rectangle r = new Rectangle
{
Fill = Brushes.Orange,
Width = 200,
Height = 100
};
Ellipse e = new Ellipse
{
Fill = Brushes.DodgerBlue,
Width = 100,
Height = 100
};
public Canvas canvas { get; set; }
public void Initialize()
{
canvas = new Canvas();
Switch(1);
}
// Here the contents of the canvas are switched
// I called it from Click events of two Buttons outside of Frame
// In your case, I imagine it will be something like this:
// public void LoadImage(string path) {...}
public void Switch(int imageNr)
{
switch (imageNr)
{
case 1:
canvas.Children.Clear();
canvas.Children.Add(r);
break;
case 2:
{
canvas.Children.Clear();
canvas.Children.Add(e);
}
break;
default:
break;
}
}
#region CONSTRUCTOR
static CanvasViewModel() { }
private CanvasViewModel() { }
private static CanvasViewModel GetAppViewModelHolder()
{
CanvasViewModel vh = new CanvasViewModel();
vh.Initialize();
return vh;
}
#endregion
#region SINGLETON Instance
private static readonly object _syncRoot = new object();
private static volatile CanvasViewModel instance;
public static CanvasViewModel Instance
{
get
{
var result = instance;
if (result == null)
{
lock (_syncRoot)
{
if (instance == null)
{
result = instance = GetAppViewModelHolder();
}
}
}
return result;
}
}
#endregion
}
Switching between images in the Click event of a Button outside of Frame:
private void Button_Click(object sender, RoutedEventArgs e)
{
CanvasViewModel.Instance.Switch(2);
}
I'm new to cocossharp. I installed cocossharp templates for visual studio, when i select a new cocossharp android game, and run the application, all I get is a black screen with a logo at the top. From the code, I believe i am supposed to get a blue screen with a label written
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get our game view from the layout resource,
// and attach the view created event to it
CCGameView gameView = (CCGameView)FindViewById(Resource.Id.GameView);
gameView.ViewCreated += LoadGame;
}
void LoadGame(object sender, EventArgs e)
{
CCGameView gameView = sender as CCGameView;
if (gameView != null)
{
var contentSearchPaths = new List<string>() { "Fonts", "Sounds" };
CCSizeI viewSize = gameView.ViewSize;
int width = 1024;
int height = 768;
// Set world dimensions
gameView.DesignResolution = new CCSizeI(width, height);
// Determine whether to use the high or low def versions of our images
// Make sure the default texel to content size ratio is set correctly
// Of course you're free to have a finer set of image resolutions e.g (ld, hd, super-hd)
if (width < viewSize.Width)
{
contentSearchPaths.Add("Images/Hd");
CCSprite.DefaultTexelToContentSizeRatio = 2.0f;
}
else
{
contentSearchPaths.Add("Images/Ld");
CCSprite.DefaultTexelToContentSizeRatio = 1.0f;
}
gameView.ContentManager.SearchPaths = contentSearchPaths;
CCScene gameScene = new CCScene(gameView);
gameScene.AddLayer(new GameLayer());
gameView.RunWithScene(gameScene);
}
}
public class GameLayer : CCLayerColor
{
// Define a label variable
CCLabel label;
public GameLayer() : base(CCColor4B.Blue)
{
// create and initialize a Label
label = new CCLabel("Hello CocosSharp", "Fonts/MarkerFelt", 22, CCLabelFormat.SpriteFont);
// add the label as a child to this Layer
AddChild(label);
}
protected override void AddedToScene()
{
base.AddedToScene();
// Use the bounds to layout the positioning of our drawable assets
var bounds = VisibleBoundsWorldspace;
// position the label on the center of the screen
label.Position = bounds.Center;
// Register for touch events
var touchListener = new CCEventListenerTouchAllAtOnce();
touchListener.OnTouchesEnded = OnTouchesEnded;
AddEventListener(touchListener, this);
}
void OnTouchesEnded(List<CCTouch> touches, CCEvent touchEvent)
{
if (touches.Count > 0)
{
// Perform touch handling here
}
}
}
I put a break point in the method thats called when the event ViewCreated is fired, the breakpoint is never hit. I tried creating the CCGameView first then then registering the eventhandler because I thought the event was firing before registering
CCGameView gameView = new CCGameView(this);
gameView.ViewCreated += LoadGame;
gameView = (CCGameView)FindViewById(Resource.Id.GameView);
then I tried calling the LoadGame method directly
CCGameView gameView = (CCGameView)FindViewById(Resource.Id.GameView);
gameView.ViewCreated += LoadGame;
LoadGame(gameView, EventArgs.Empty);
but this resulted in a null exception for the gameView.ContentManager.
My only other suspicion is the emulator itself, perhaps it needs something installed extra, however for a normal xamarin android project it works perfectly. Iv also tried looking at the various examples on Xamarin but they all use Application Delegate, which if i'm not mistaken, was the old way of doing thing. If anyone can help, Id appreciate it. Thanks
It was an emulator issue, had to check the Use Host GPU option on the emulator. On the Android Virtual Device Manager where I can select my created emulators, I selected an emulator I had created, then instead of starting it, I first Edited it, that's where I found the option (Since I had already created some emulators). The answer is here
So I am creating a treeview selector with C#/GTKSharp. I have the basic tree view selector functionality working: The data is loaded into my model and I can click on a node to collapse/expand.
The part I can't work out is how to tell the cell renderer to display the collapse/expand toggle button. In the examples it appears as a triangle that points right or down depending on whether the node is opened or collapsed. I just have a blank space that works as expected as I click but shows nothing.
One possibility is that I have a white on white text issue but I doubt it as my labels show up fine and I have not done any formatting yet.
I tried adding code for ShowExpanders but that was already true.
TreeView = new Gtk.TreeView();
// We add the event handlers (i.e. the control part) to the tree
TreeView.RowActivated += SelectorActivated; //On double click
TreeView.Selection.Changed += SelectorSelected; // On select (single click)
// Raise a context menu here??
// Connect to the ButtonPressEvent
// Raise a popup button
// Create columns [View]
Gtk.TreeViewColumn TreeViewColumTitle = new Gtk.TreeViewColumn();
TreeViewColumTitle.Title = "Profile";
Gtk.CellRendererText NameCellTitle = new Gtk.CellRendererText();
TreeViewColumTitle.PackStart(NameCellTitle, true);
TreeViewColumTitle.SetCellDataFunc(NameCellTitle, new Gtk.TreeCellDataFunc(RenderTitle));
NameCellTitle.Mode = CellRendererMode.Activatable;
// Populate the model
// Note that we could dispense with this step if we generated an ITreeModel
// interface in the Object class.
BindModel(Model);
// Attach everything to the pane
TreeView.Model = GTKModel;
TreeView.AppendColumn(TreeViewColumTitle);
TreeView.ShowExpanders = true;
TreeView.ExpanderColumn.Visible = true;
...
private void BindModel(Model Model) {
GTKModel = new Gtk.TreeStore(typeof(Object));
foreach (Object Object in Model.Selector) {
var BindingData = new BindingDataGTK(this, Object);
BindingData.Iter = GTKModel.AppendValues(Object);
Object.BindingData = BindingData;
BindChildren(GTKModel, BindingData);
}
}
private void BindChildren(TreeStore TreeStore, BindingDataGTK ObjectBinding) {
foreach (var Child in ObjectBinding.Object) {
var BindingData = new BindingDataGTK(this, Child);
BindingData.Iter = TreeStore.AppendValues(ObjectBinding.Iter, Child);
Child.BindingData = BindingData;
BindChildren(TreeStore, BindingData);
}
}
private void RenderTitle(Gtk.TreeViewColumn Column, Gtk.CellRenderer Cell,
Gtk.ITreeModel GTKModel, Gtk.TreeIter Iter) {
Object Object = (Object)GTKModel.GetValue(Iter, 0);
(Cell as Gtk.CellRendererText).Text = Object.Title;
Console.WriteLine("Render {0}", Object.Title);
}
So far as I know this is pretty much an automatic feature, I don't think anything special is needed to make it happen (I've certainly never needed to). You might want to try using a TreeIter to construct your tree instead?
E.g. assuming you already have a TreeView on your form with 0 (zero) columns in it called "treeview" and a list of "MyObject"s called "myListOfObjects"...
treeview.AppendColumn ("Some Title", new CellRendererText(), "text", 0);
Gtk.TreeStore _ts = new TreeStore (typeof(string));
foreach (IMyObject _mo in myListOfObjects) {
Gtk.TreeIter _it = _ts.AppendValues (_mo.SomeText);
RecurseInto (_ts, _it, _mo);
}
treeview.Model = _ts;
...
void RescureInto(Gtk.TreeStore ts, Gtk.TreeIter it, IMyObject mo)
{
foreach (IMyObject _child_mo in mo.Children) {
Gtk.TreeIter _it = ts.AppendValues (it, _child_mo.SomeText);
RecurseInto (ts, _it, _child_mo);
}
}
In theory this should work fine.
When I set the MonoTouch.Dialog background color to uiclear(transparent), it throw an exception, why? and How to set it to transparent.
Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object MyDialogViewController.LoadView () [0x00016] in MyDialogViewController.cs: ParentViewController.View.BackgroundColor = UIColor.Clear
public class MyDialogViewController: DialogViewController
{
public MyDialogViewController (RootElement root) : base (root)
{
}
public override void LoadView()
{
base.LoadView ();
this.TableView.BackgroundColor = UIColor.Clear;
ParentViewController.View.BackgroundColor = UIColor.Clear;
}
}
public void xxxxx(){
var menu = new RootElement(""){
new Section ("Demo"){
new EntryElement("Name", "",""),
},
};
var menuDVC = new MyDialogViewController (menu) {
Autorotate = true
};
this.View.AddSubview(menuDVC.View);
}
The NullReferenceException most likely occurs because ParentViewController is null.
Depending on how your MyDialogViewController is showed this might be due to using the wrong property and a recent, iOS5, change:
Prior to iOS 5.0, if a view did not have a parent view controller and was being presented, the presenting view controller would be returned. On iOS 5, this behavior no longer occurs. Instead, use the presentingViewController property to access the presenting view controller.
However if the MyDialogViewController is the window's RootViewController then it's normal for those properties to be null. In this case simply using UIColor.Clear on the TableView get me a black background (I had nothing there) so it should be enough for MT.D part. If you have a parent then you can try to set it's background color to clear (if needed) before displaying your MyDialogViewController.
is it possible to define (not switch) VisualStates in CodeBehind?
I'm creating an Adorner, that draws some rectangles in OnRender. What I'd like to do is to change the Opacity of these Rectangles by it's Property IsMouseOver (say from 0.3 to 0.8).
In any control with a visual tree I'd add some VisualStates and switch those with a DataStateBehavior. How do I do this with an Adorner?
this is entirely possible.
if anyone is interested here is how I did it:
public class MyAdorner: Adorner
{
ctor (...):base(...)
{
...
var storyboard = new Storyboard();
var doubleAnimation = new DoubleAnimation(0.2,new Duration(TimeSpan.Zero));
Storyboard.SetTarget(doubleAnimation,this);
Storyboard.SetTargetProperty(doubleAnimation,new PropertyPath(RectOpacityProperty));
storyboard.Children.Add(doubleAnimation);
var storyboard2 = new Storyboard();
var doubleAnimation2 = new DoubleAnimation(0.5, new Duration(TimeSpan.Zero));
Storyboard.SetTarget(doubleAnimation2, this);
Storyboard.SetTargetProperty(doubleAnimation2, new PropertyPath(RectOpacityProperty));
storyboard2.Children.Add(doubleAnimation2);
var stateGroup = new VisualStateGroup { Name = "MouseOverState" };
stateGroup.States.Add(new VisualState { Name = "MouseOut", Storyboard = storyboard });
stateGroup.States.Add(new VisualState { Name = "MouseOver", Storyboard = storyboard2});
var sgs = VisualStateManager.GetVisualStateGroups(this);
sgs.Add(stateGroup);
var dsb = new DataStateBehavior
{
Value = true,
FalseState = "MouseOut",
TrueState = "MouseOver"
};
BindingOperations.SetBinding(dsb, DataStateBehavior.BindingProperty, new Binding {Source = this, Path = new PropertyPath(IsMouseOverProperty)});
dsb.Attach(this);
}
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawRectangle(_mouseOverBrush, _pen, _rects[i]); //mouseoverbrush is a Solidcolorbrush
}
public double RectOpacity
{
get { return (double)GetValue(RectOpacityProperty); }
set { SetValue(RectOpacityProperty, value); }
}
public static readonly DependencyProperty RectOpacityProperty =
DependencyProperty.Register("RectOpacity", typeof(double), typeof(XmlNodeWrapperAdorner), new FrameworkPropertyMetadata(0.0,FrameworkPropertyMetadataOptions.AffectsRender,(o, args) =>
{
var adorner = o as MyAdorner;
adorner._mouseOverBrush.Color = Color.FromArgb((byte)((double)args.NewValue * 0xFF), 0xFF, 0xBE, 0x00);
}));
}
pretty straightforward actually.
key points here are:
you cannot set the VisualStateGroups attached property. you have to get the collection and then add your own group
you cannot do new DataStateBehavior{Binding = new Binding(...){...}} as this will assign not bind some value to the property. As Behvior<T> doesn't derive from FrameworkElement you also can't use SetBinding but have to use the BindingOperations class.
for automatic rerendering when the property changes keep in mind to set FrameworkPropertyMetadataOptions.AffectsRender.
Since you're already creating a custom adorner with your own behavior, i would suggest that you override the MouseOver method of the adorner and change the opacity of your rectangles there...
another way would be to listen to your own PropertyChanged event and monitor the change in IsMouseOver, or maybe monitor the MouseMove event...
If you could add States in code, tools such as Blend would have to run all code in all possible configurations to find out what states are present/possible.
So, no, you can't do this in code. It only possible using attributes.
EDIT
I stand corrected but the problem mentioned still remains. This technique is not useful for designers.