This question is related to this: Keep Codename One components in invalid positions after app stop/resume
In the linked question, the solution proposed is to use a fake layout. I tried that, but it produces side effects when I try to restore the original layouts.
I tried a completely different approach, that works fine on Android. My question is why the following code works well in Android only (it doesn't work on iPhone, that seems to ignore that code) and if there are small changes that make that code working also on iPhone.
The code:
private Map<Component, Dimension> layeredPaneCmps = new HashMap<>();
public void start() {
if (current != null) {
current.show();
layeredPaneRestore(); // it works on Android, but not on iOS
return;
}
[...]
}
public void stop() {
current = getCurrentForm();
if (current instanceof Dialog) {
((Dialog) current).dispose();
current = getCurrentForm();
}
layeredPaneSave(null);
}
/**
* Save the position of all layered pane components in a recursive way: just
* invoke with null as cnt.
*
* #param cnt
*/
private void layeredPaneSave(Container cnt) {
if (cnt == null) {
layeredPaneCmps.clear();
cnt = Display.getInstance().getCurrent().getLayeredPane(this.getClass(), true);
}
for (int i = 0; i < cnt.getComponentCount(); i++) {
layeredPaneCmps.put(cnt.getComponentAt(i), new Dimension(cnt.getComponentAt(i).getX(), cnt.getComponentAt(i).getY()));
if (cnt.getComponentAt(i) instanceof Container) {
layeredPaneSave((Container) cnt.getComponentAt(i));
}
}
}
/**
* Restores all layered pane components in their position and repaints them.
*/
private void layeredPaneRestore() {
Container layeredPane = Display.getInstance().getCurrent().getLayeredPane(this.getClass(), true);
for (Component cmp : layeredPaneCmps.keySet()) {
cmp.setX(layeredPaneCmps.get(cmp).getWidth());
cmp.setY(layeredPaneCmps.get(cmp).getHeight());
cmp.repaint();
}
layeredPane.repaint();
}
Android and iOS have very different suspend/resume behaviors where iOS tries to minimize repaints and back-grounding while Android constantly suspends/resumes. I would suggest logging in the stop()/start() method to make sure they aren't invoked multiple times.
Notice that you shouldn't invoke repaint() it would be invoked for you. Since a repaint() might trigger a layout this could be a problem. Also the repaint() of the parent component loops into painting the components so layeredPane would be enough and doesn't require also cmp.repaint();.
Related
This happens on Android only. Videos work properly on iOS and Simulator.
The problem is that when my videos are loaded into the MediaPlayer, the displayed frame is and remains completely black. After pressing play, the video displays correctly. By pressing pause, the frame on which the pause occurred is shown correctly. However, if the app switches to background and then back to foreground, the paused video goes back to completely black.
How can I fix this problem?
The relevant part of my code is more or less as follows:
Media video = MediaManager.createMedia(videoFilePath, true, () -> {
Runnable callback = runOnVideoCompletion.get();
if (callback != null) {
callback.run();
}
});
video.setNativePlayerMode(false);
video.prepare();
MediaPlayer mediaPlayer = new MediaPlayer(video) {
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
mediaPlayer.setHideNativeVideoControls(true);
mediaPlayer.setMaximize(false);
mediaPlayer.hideControls();
mediaPlayer.setAutoplay(false);
Container mediaPlayerCnt = new Container(new LayeredLayout(), "NoMarginNoPadding") {
#Override
public Dimension getPreferredSize() {
return new Dimension(size, size);
}
};
mediaPlayerCnt.add(mediaPlayer);
[...]
UPDATE: please see the comment Reduce the impact of videos in memory to prevent crashes, since there is a better approach than the solution I proposed here.
While waiting for a proper solution, I came up with a workaround that seems to solve the problem perfectly on my Android phone.
This code is in a method invoked to create each video and is therefore executed before form.show(), so the UITimer runnable is invoked about half a second after the form is shown.
/*
* Workaround for the issue: https://stackoverflow.com/q/66144759
* "video" is a Media instance returned by MediaManager.createMedia
* This workaround is for Android only and it assumes we don't use autoplay
*/
if (form != null && DeviceUtilities.isAndroidNative()) {
UITimer.timer(500, false, form, () -> {
CN.invokeAndBlock(() -> {
video.play();
video.setVolume(0);
while (!video.isPlaying()) {
/*
* When in a scrollable container there are many videos,
* play does not start immediately, but only when the
* video becomes visible, so we must wait.
*/
Util.sleep(100);
}
Util.sleep(100); // extra wait to be sure that the waiting is not zero
video.pause();
video.setVolume(100);
});
form.addShowListener(l -> {
CN.invokeAndBlock(() -> {
video.play();
video.setVolume(0);
while (!video.isPlaying()) {
// same as above
Util.sleep(100);
}
Util.sleep(100); // as above
video.pause();
video.setVolume(100);
});
});
});
}
Oddly when I play a video using MediaPlayer in a non-native way, after a few seconds (depending on the mobile phone settings) the power saving mode starts, which turns the screen off. This is an unwanted behavior, because when playing a video the screen must never turn off or dim. I don't understand if this is a Codename One bug or a feature, however it happens both on Android and iOS.
Apparently I fixed the problem using CN.setScreenSaverEnabled(false);. My problem, however, is that it doesn't work all the time. The following code sometimes makes the screen stay active, sometimes it doesn't. I don't understand why sometimes it works and sometimes it doesn't.
To better understand the following code, videoUrl is an HLS stream.
private void playVideo(Form parent, String videoUrl) {
CN.setScreenSaverEnabled(false);
Form player = new Form(new BorderLayout());
player.getToolbar().setBackCommand("Back", Toolbar.BackCommandPolicy.ALWAYS, e -> {
if (mp != null) {
mp.getMedia().cleanup();
}
CN.setScreenSaverEnabled(true);
parent.showBack();
});
player.add(BorderLayout.CENTER, FlowLayout.encloseCenterMiddle(
new SpanLabel("Stream will start playing automatically when it is live")));
player.addShowListener(l -> {
while (!Util.downloadUrlToStorage(videoUrl, "temp.m3u8", false)) {
CN.invokeAndBlock(() -> Util.sleep(1000));
}
try {
// note that we cannot play the locally downloaded m3u8
Media video = MediaManager.createMedia(videoUrl, true, () -> {
// completion handler, it's invoked when the stream connection is lost
if (mp != null) {
mp.getMedia().cleanup();
}
CN.setScreenSaverEnabled(true);
parent.showBack();
});
video.setNativePlayerMode(false);
if (mp != null) {
mp.getMedia().cleanup();
}
mp = new MediaPlayer(video);
mp.setAutoplay(true);
mp.setHideNativeVideoControls(true);
mp.setMaximize(false);
player.removeAll();
player.add(BorderLayout.CENTER, mp);
player.revalidate();
} catch (Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading straming");
}
});
player.show();
}
I am using two AutocompleteTextFilters as depended filters. I want the second one filter to change its options depending on the suggestion of the first filter.
I have bind an event listener on the first filter so as when it loose focus it triggers a proccess on the second filter.
The proble is that the second filter never changes its options. I even have setup hardcoded values in case somethig was wrong on my code but no luck.
The code I use is below:
public CreateSubmission(com.codename1.ui.util.Resources resourceObjectInstance, Map<String, ProjectType> projectTypes) {
this.projectTypes = projectTypes;
initGuiBuilderComponents(resourceObjectInstance);
gui_ac_projecttype.clear();
gui_ac_projecttype.setCompletion( this.projectTypes.keySet().toArray( new String[0]) );
gui_ac_projecttype.addFocusListener( new ProjectTypeFocusListener( this ));
gui_ac_steps.setCompletion( new String[]{"t10", "t20"});
}
public void makeSteps (String selection) {
ProjectType projectType = this.projectTypes.get( selection );
if (projectType != null) {
this.selectedProjectType = selection;
int length = projectType.projectSteps.length;
String[] steps = new String[ length ];
for(int i =0; i < length; i ++) {
steps[i] = projectType.projectSteps[i].projectStep;
}
// String[] s = gui_ac_steps.getCompletion();
gui_ac_steps.setCompletion( new String[]{"t1", "t2"} );
gui_ac_steps.repaint();
}
else {
}
}
public class ProjectTypeFocusListener implements FocusListener{
private CreateSubmission parent;
public ProjectTypeFocusListener( CreateSubmission parent ) {
this.parent = parent;
}
#Override
public void focusGained(Component cmp) {
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
#Override
public void focusLost(Component cmp) {
this.parent.makeSteps (
((AutoCompleteTextField)cmp).getText()
);
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
On the above code the initialization happens on "public CreateSubmission" method.
"gui_ac_projecttype" is the first AutocompletionTextField that triggers the whole proccess through it's FocusListener handler (class ProjectTypeFocusListener )
"gui_ac_steps" is the second AutocompleteTextField filter that must change its values. On the code above I initialize it's suggestions to "t10", "t20". Those two values are shown correctly.
Later from iside the FoculListenerHandler's method "ProjectTypeFocusListener.focusLost" I call method "makeSteps" which sets the suggestion options to "t1", "t2 and then I repaint the component. These two last values are never shown. It remains on the first values "t10", "t20".
The Strange thing is that in debugger when I ask gui_ac_steps.getCompletion(); to see the current options ( the code that is commentd out into makeSteps method) I get the correct values "t1", "t2".
But on the screen it keeps showing "t10", "t20".
any help is aprreciated.
You shouldn't do anything "important" in a focus listener. Especially not with a text field. They are somewhat unreliable because the text field switches to native editing and in effect transfers the focus there. The problem is that some events are delayed due to the back and forth with the native editing so by the time the focus event is received you've moved on to the next field.
Try something like this for this specific use case https://www.codenameone.com/blog/dynamic-autocomplete.html
I have started working with DirectX in WPF app. My first step was to use simple library:
SharpdDX.WPF. Based on samples I've implemented WPF control drawing simple line. SharpDX.WPF uses D3DImage to render images in WPF.
Unfortunately application's memory increasing all time.
I implemented class TestControlRenderer : D3D10.
Vertex shader is initialized like:
var sizeInBytes = dataLength * sizeof(int) * 3;
var bufferDescription = new BufferDescription(
sizeInBytes,
ResourceUsage.Dynamic,
BindFlags.VertexBuffer,
CpuAccessFlags.Write,
ResourceOptionFlags.None);
using (var stream = new DataStream(sizeInBytes, true, true))
{
stream.Position = 0;
_graphDataVertexBuffer = new SharpDX.Direct3D10.Buffer(Device, stream, bufferDescription);
}
Device.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(_graphDataVertexBuffer, sizeof(int) * 3, 0));
Device.InputAssembler.PrimitiveTopology = PrimitiveTopology.LineStrip;
Then constant buffer with parameters used in shader:
_controlInfoConstantBuffer = new ConstantBuffer<ControlParamsShaderData>(Device);
Device.VertexShader.SetConstantBuffer(0, _controlInfoConstantBuffer.Buffer);
To init animation Reset method was overriden like that:
base.Reset(args);
if (args.RenderSize.Width == 0) return;
_drawArgs = args;
InitVertexBuffer(dataLength);
_controlInfoConstantBuffer.Value = new ControlParamsShaderData
{
SamplesInControl = dataLength,
MinSignalDataY = -1500,
MaxSignalDataY = 1500
};
Device.VertexShader.SetConstantBuffer(0, _controlInfoConstantBuffer.Buffer);
The last step is RenderScene method:
public override void RenderScene(DrawEventArgs args)
{
if (args.RenderSize.Width == 0) return;
Device.ClearRenderTargetView(RenderTargetView, Color.Transparent);
using (var stream = _graphDataVertexBuffer.Map(MapMode.WriteDiscard, SharpDX.Direct3D10.MapFlags.None))
{
for (int i = 0; i < Data.Length; i++)
{
stream.Write(new Vector3(i, Data[i], 0));
}
}
_graphDataVertexBuffer.Unmap();
Device.Draw(Data.Length, 0);
}
Rendering is controlled by DispatcherTimer where OnTickMethod updates array with points coordinates and then invoke Render() method.
My question is simply, is that memory leak or something is created on each render iteration?
I don't change backbuffer or create another objects. Only change Data array, update it to GPU and Shaders process it to display.
My case is to display about 30 wpf controls width DirectX on one screen. Controls are with simple but realtime animation. Is that possible in that way?
Most likely you are leaking resources. You can see this by setting the static configuration property
SharpDX.Configuration.EnableObjectTracking = true;
then calling
SharpDX.Diagnostics.ObjectTracker.ReportActiveObjects()
at various points in your application lifetime to see if anything is leaking (at least on the SharpDX side). You can edit your code to make sure to dispose these objects. Only enable object tracking while debugging - it hurts performance.
SharpDX used to release COM objects when the finalizer ran if the object had not already been Diposed (at least as in version 2.4.2), but later disabled that (they detail why in one of their changelogs, I forget which one).
Additionally, DirectX requires that you release objects in the reverse order they were created - this can create hard-to-debug memory leaks. So when your code is
var device = new Devie(...);
var effect = new Effec(Device, byteCode);
technique = effect.GetTechniqueByName(techniqueName);
inputLayout = new InputLayout(Device, _technique.GetPassByIndex(0).Description.Signature, ...);
then your dispose code has to be
_inputLayout.Dispose();
_technique.Dispose();
_effect.Dispose();
_device.Dispose();
I am making a 3D Game with WPF in VB, and I am using a ScrennSpaceLines3D Object I found
http://3dtools.codeplex.com/releases/view/2058
but when I try to remove a line I added to the viewport by using
mainViewport.Children.RemoveAt(i)
it gives a NullExceptionError. I have read that this is because it does not totally come off the rendering queue. There have been fixes for c#, but I have yet to find one that works with VB. Is there a way to make this work or possibly draw a line in 3D space some other way? I find it quite ridiculous that VB doesn't even have a way to easily draw 3D lines...
Remove ScreenSpaceLines3D :
foreach (ScreenSpaceLines3D line3D in lines3DList)
{
lines3D.Points.Clear(); // Very importante
_viewport3D.Children.Remove(lines3D);
}
I'm a bit late to the party but i'm having the same issues.
The access violation occurs because each instance registers an event handler to the Rendering event of the composition target
public ScreenSpaceLines3D()
{
...
CompositionTarget.Rendering += OnRender; // <-- this line
}
but forgets to remove it when the instance is removed from the scene.
So to fix this you need to touch the source code:
public ScreenSpaceLines3D()
{
...
// event registration removed
}
private bool AttachedToCompositionTargetRendering { get; set; }
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
base.OnVisualParentChanged(oldParent);
var parent = VisualTreeHelper.GetParent(this);
if (parent == null)
{
if (AttachedToCompositionTargetRendering)
{
CompositionTarget.Rendering -= OnRender;
AttachedToCompositionTargetRendering = false;
}
}
else
{
if (!AttachedToCompositionTargetRendering)
{
CompositionTarget.Rendering += OnRender;
AttachedToCompositionTargetRendering = true;
}
}
}