win8 Frame delegate - windows-runtime

I have a frame which has a function that updates the frame when an event in another class is raised.
I have the class 'IRCClient' and 'MainFrame'. The IRCClient class has an event 'OnMessageRecvd', the MainFrame has a function 'HandleNewMessageReceived'. In the MainFrame class I have the variables 'CurrentServer' and 'CurrentChannel' to indicate what channel on what server is currently shown to the user.
Now, when I set the 'CurrentServer' and 'CurrentChannel' in the callback of a button, they have a value and all is fine. However, when the 'HandleNewMessageReceived' function is called by the 'OnMessageRecvd' event of IRCClient, the CurrentServer and CurrentChannel are both equal to any value (null) stated in the constructor of MainFrame.
Does anyone have an idea what the source of this behavior is? Thanks a lot in advance.
EDIT:
Below is the code, I've only posted the code in question (any function that uses the CurrentChannel and CurrentServer properties) and snipped away unrelated code.
// Main page, shows chat history.
public sealed partial class MainPage : LIRC.Common.LayoutAwarePage
{
private uint maxMessages;
IRCClient ircc;
IRCHistory irch;
string CurrentServer, CurrentChannel;
// Does all the setup for this class.
public MainPage()
{
this.InitializeComponent();
ircc = App.ircc; // This is a global variable in the 'App' class.
ircc.OnMessage += NewMessageReceived;
irch = App.irch; // This is also a global variable in the 'App' class.
currentChannel = currentServer = null;
}
// Restores the previous state.
protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
if (pageState != null)
{
if(pageState.ContainsKey("viewedChannel"))
{
// Retrieve required info.
string[] viewedChannelTokens = (pageState["viewedChannel"] as string).Split('.');
CurrentChannel = viewedChannelTokens[0];
CurrentServer = viewedChannelTokens[1];
// If the saved channel or server got corrupt
if (string.IsNullOrEmpty(CurrentChannel) || string.IsNullOrEmpty(CurrentServer))
{
// Check if a channel is open, if so, select it.
*snip* // Non-relevant code.
}
// Clear and load required history.
ClearHistory();
if(CurrentServer != null && CurrentChannel != null)
LoadHistory(CurrentServer, CurrentChannel);
}
}
// Create buttons that switch to a channel
*Snip* // Calls AddChannelButton
}
// Creates a button that, when clicked, causes the ChatHistoryView to display the ChannelHistory.
void AddChannelButton(string Server, string Channel)
{
Button btn = new Button();
btn.Content = Channel + "\n" + Server;
btn.Width = 150;
// A function to switch to another channel.
btn.Click += (e, s) =>
{
ClearHistory(); // Clears the ChatHistoryVi.ew field.
LoadHistory(Server, Channel); // Does the actual loading of the channel history
CurrentChannel = Channel;
CurrentServer = Server;
};
ChannelBar.Children.Add(btn);
}
// The function that is called by the IRCClient.OnMessageRecv event.
public void NewMessageReceived(ref DataWriter dw, IRCServerInfo ircsi, IRCClient.RecvMessage recvmsg)
{
if (ircsi.Name == CurrentServer && CurrentChannel == recvmsg.recipient)
{
AddMessage(DateTimeToTime(DateTime.UtcNow), recvmsg.author, recvmsg.message);
}
}
}
// Responsible for creating, managing and closing connections.
public class IRCClient
{
// A structure that describes a message.
public struct RecvMessage
{
public string author; // Nickname
public string realName;
public string ipAddress;
public string recipient; // Indicates in what channel or private converstion.
public string message; // The actual message
};
// Describes how a function that handles a message should be declared.
public delegate void MessageHandler(ref DataWriter dw, IRCServerInfo ircsi, RecvMessage msg);
// Gets raised/called whenever a message was received.
public event MessageHandler OnMessage;
}

It's not clear what is happening from what you said, but if the variables are set to the values you set in the constructor when you check them - it means that either you have not changed them yet by the time you are expecting them to be changed or you set the value of some other variables instead of the ones you thought you had.
These are only guesses though and you can't expect more than guesses without showing your code.

Related

My static variables in my Blazor Server app are keeping their values, even if I refresh the page or even I close the tab and login again. Why?

I have a Blazor server app. Some variables on a specific razor page (main.razor) are defined as static because I want that these variables keep their values when the client navigates to other pages in the same project and comes back again to main.razor. So far it is working good.
But when I refresh the complete page, or even close the tab and reopen my app (login again), I see that the static variables still keep their values. How can prevent this? Of course I want that the values return to their default values (like 0 or ""), when the client makes a login or refreshes the page with F5. How can I do that?
I have defined the related variables in the following way:
private static StringBuilder log = new StringBuilder();
public static string testvar1= "";
public static int testvar2= 0;
Statics exist for the lifetime of the application instance which explains the behaviour you see.
You need to be maintaining state. At one end of the spectrum you can implement a State Management system such as Fluxor. At the other just create a user class, set it up as a service and inject it as a Scoped Service. Or you can build a middle-of-the-road solution.
This is mine.
A generic UIStateService that maintains a Dictionary of (state)objects against a Guid.
public class UIStateService
{
private Dictionary<Guid, object> _stateItems = new Dictionary<Guid, object>();
public void AddStateData(Guid Id, object value)
{
if (_stateItems.ContainsKey(Id))
_stateItems[Id] = value;
else
_stateItems.Add(Id, value);
}
public void ClearStateData(Guid Id)
{
if (_stateItems.ContainsKey(Id))
_stateItems.Remove(Id);
}
public bool TryGetStateData<T>(Guid Id, out T? value)
{
value = default;
if (Id == Guid.Empty)
return false;
var isdata = _stateItems.ContainsKey(Id);
var val = isdata
? _stateItems[Id]
: default;
if (val is T)
{
value = (T)val;
return true;
}
return false;
}
}
Set it up as a service:
builder.Services.AddScoped<UIStateService>();
Next define a simple template ComponentBase page that contains the common page code:
using Blazr.UI;
using Microsoft.AspNetCore.Components;
namespace BlazorApp2.Pages
{
public class StatePage : ComponentBase
{
// this provides a guid for this specific page during the lifetime of the application runtime
// we use this as the reference to store the state data against
private static Guid RouteId = Guid.NewGuid();
[Inject] protected UIStateService UIStateService { get; set; } = default!;
protected void SaveState<T>(T state) where T : class, new()
{
if (RouteId != Guid.Empty)
this.UIStateService.AddStateData(RouteId, state);
}
protected bool GetState<T>( out T value) where T : class, new()
{
value = new T();
if (RouteId != Guid.Empty && this.UIStateService.TryGetStateData<T>(RouteId, out T? returnedState))
{
value = returnedState ?? new T();
return true;
}
else
return false;
}
}
}
And use it in a page:
#page "/"
#inherits StatePage
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class="p-2">
<button class="btn btn-primary" #onclick=SetData>Set Data</button>
</div>
<div class="p-3 text-primary">
State Time : #stateData.StateTime;
</div>
#code {
private MyStateData stateData = new MyStateData();
protected override void OnInitialized()
{
if (this.GetState<MyStateData>(out MyStateData value))
this.stateData = value;
else
this.SaveState<MyStateData>(this.stateData);
}
private void SetData()
{
this.stateData.StateTime = DateTime.Now.ToLongTimeString();
SaveState<MyStateData>(this.stateData);
}
public class MyStateData
{
public string StateTime { get; set; } = DateTime.Now.ToLongTimeString();
}
}
You can now navigate around the application and the state will be maintained for the page.
You can apply an observer/notification pattern to the state object to trigger automatic state updates if you wish.

JavaFX : TableView inside Dialog has duplicate items

I have an issue with my TableView and its items. I have created a small Dialog window to display warnings about my app, and inside the Dialog I have a TableView which displays the name of the warning and some information about it upon clicking on a button.
I have created a WarningUtil class (Singleton pattern) just to open / close the Dialog. The relevant code follows.
The constructor of the WarningUtil class (called once only) :
private WarningUtil(RootCtrl rootCtrl) {
this.rootCtrl = rootCtrl;
warnings = new HashMap<>();
setupWarningCallbacks(); // not relevant
setupTable();
setupColumns(); // not relevant
setupDialog();
}
The function managing the construction of the Dialog :
private void setupTable() {
// create the content pane
content = new AnchorPane(); // class variable - reference needed for further uses
content.setPrefSize(480, 240);
// create the root nodes of the view (table + 2 columns)
warningTable = new TableView<>(); // class variable - reference needed for further uses
warnDescriptionCol = new PTableColumn<>(); // class variable - reference needed for further uses
warnDetailsCol = new PTableColumn<>(); // class variable - reference needed for further uses
// settings anchors to keep the ration between dialog <-> table
AnchorPane.setBottomAnchor(warningTable, 15.0);
AnchorPane.setTopAnchor(warningTable, 15.0);
AnchorPane.setLeftAnchor(warningTable, 15.0);
AnchorPane.setRightAnchor(warningTable, 15.0);
// setting up the columns
warnDescriptionCol.setText(i18n("label.desc"));
warnDetailsCol.setText(i18n("label.details"));
warnDescriptionCol.setPercentageWidth(0.7);
warnDetailsCol.setPercentageWidth(0.3);
warnDescriptionCol.setResizable(false);
warnDetailsCol.setResizable(false);
// adding nodes to containers
warningTable.getColumns().addAll(warnDescriptionCol, warnDetailsCol);
content.getChildren().add(warningTable);
}
The function used to create the Dialog and set the content :
private void setupDialog() {
// creation and saving of the dialog in a variable reused later
warningDialog = DialogFactory.getInstance(rootCtrl.getPrimaryStage()).createWarningDialog();
warningDialog.getDialogPane().setContent(content);
warningDialog.getDialogPane().getScene().getWindow().sizeToScene();
}
// The DialogFactory function creating the dialog
public Dialog createWarningDialog(){
CustomDialog dialog = new CustomDialog(rootStage);
dialog.setTitle(i18n("warning.description"));
ButtonType cancelBt = new ButtonType(i18n("button.close"), ButtonData.OK_DONE);
dialog.getDialogPane().getButtonTypes().add(cancelBt);
return dialog.setupLayout();
}
The Main class is in charge of loading the warnings (stored in a .json file and deserialized upon starting the app). For now, the file only contains one entry.
When I click on my Warning button, the following function is called :
public void showWarnings() {
warningTable.getItems().clear(); // BP
warningTable.setItems(FXCollections.observableArrayList(warnings.values()));
warningDialog.showAndWait();
}
What happens is the following : When I have only one entry in my .json file, the first time I click on the button, only one warning is shown. If I click a second time, a second entry appears (the same) which should not be possible because of the following reasons :
Logic constraint : warnings.values() comes from an HashMap where the key is the type of the warning (WarningType class) > Not possible to have two identical keys
Debugging : When I set a breakpoint at "//BP", I clearly see that the warningTable has one item, and after clear the number of items is zero
Debugging : Still with the same breakpoint, I also check that warnings.values() has only one item, which is the case
After five clicks on the button, the Dialog clearly shows something is bugging.
More surprisingly, when I add a second warning (different from the first one, another type), the problem does not occur : No duplicates, warnings are correctly displayed and no matter how many times I open the window.
My question is : Could that be that the way I am creating this warning dialog leads to uncommon errors ? If so, why isn't it the case with two warnings ?
EDIT Include of the cellFactories / cellValueFactories
private void setupColumns() {
warnDescriptionCol.setCellFactory(new Callback<TableColumn<CustomWarning, String>, TableCell<CustomWarning, String>>() {
#Override
public TableCell<CustomWarning, String> call(TableColumn<CustomWarning, String> param) {
TableCell<CustomWarning, String> cell = new TableCell<CustomWarning, String>() {
#Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Label label = new Label(item);
setGraphic(label);
}
}
};
return cell;
}
});
warnDetailsCol.setCellFactory(new Callback<TableColumn<CustomWarning, CustomWarning>, TableCell<CustomWarning, CustomWarning>>() {
#Override
public TableCell<CustomWarning, CustomWarning> call(TableColumn<CustomWarning, CustomWarning> param) {
TableCell<CustomWarning, CustomWarning> cell = new TableCell<CustomWarning, CustomWarning>() {
#Override
protected void updateItem(CustomWarning item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
Button button = new Button(i18n("button.view"));
button.getStyleClass().add("save");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
showWarning(item);
}
});
setGraphic(button);
}
}
};
return cell;
}
});
warnDescriptionCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, String>, ObservableValue<String>>() {
TableViewObjectWrapper<CustomWarning, String> wrapper = new TableViewObjectWrapper<CustomWarning, String>() {
#Override
public String getData() {
return getModel().getTitle();
}
};
#Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<CustomWarning, String> param) {
return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
}
});
warnDetailsCol.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<CustomWarning, CustomWarning>, ObservableValue<CustomWarning>>() {
TableViewObjectWrapper<CustomWarning, CustomWarning> wrapper = new TableViewObjectWrapper<CustomWarning, CustomWarning>() {
#Override
public CustomWarning getData() {
return getModel();
}
};
#Override
public ObservableValue<CustomWarning> call(TableColumn.CellDataFeatures<CustomWarning, CustomWarning> param) {
return new ReadOnlyObjectWrapper<>(wrapper.setModel(param.getValue()).getData());
}
});
}
You have to clear your cells in the cell factory if the cell is empty, as explained in the documentation:
It is very important that subclasses of Cell override the updateItem method properly, as failure to do so will lead to issues such as blank cells or cells with unexpected content appearing within them. Here is an example of how to properly override the updateItem method:
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
if (empty || item == null) {
setText(null);
setGraphic(null);
} else {
setText(item.toString());
}
}
Note in this code sample two important points:
We call the super.updateItem(T, boolean) method. If this is not done, the item and empty properties are not correctly set, and you are likely to end up with graphical issues.
We test for the empty condition, and if true, we set the text and graphic properties to null. If we do not do this, it is almost guaranteed that end users will see graphical artifacts in cells unexpectedly.
Since the cells are reused, you have to clear the graphic if it has become empty, not just set it if it's not.

AutoMapper - passing parameter to custom resolver weird behavior

Although I'm relatively new to AutoMapper I'm using it in a small project I'm developing. I've never had problems using it before but now I'm facing some weird behavior passing parameters to a Custom Resolver.
Here's the scenario: I get a list of messages from my repository and then map those to a frontend friendly version of it. Nothing fancy, just some normal mapping between objects. I have a field in that frontend object that tells if a certain user already voted for that message and that's what I'm using the Custom Resolver for (it's that second "ForMember"):
public List<SupportMessageUi> GetAllVisible(string userId)
{
Mapper.CreateMap<SupportMessage, SupportMessageUi>()
.ForMember(dest => dest.Votes,
opt => opt.ResolveUsing<SupportMessageVotesResolver>())
.ForMember(dest => dest.UserVoted,
opt => opt.ResolveUsing<SupportMessagesUserVotedResolver>()
.ConstructedBy(() => new SupportMessagesUserVotedResolver(userId)));
var messages = _unitOfWork.MessagesRepository.Get(m => m.Visible);
var messagesUi = Mapper.Map<List<SupportMessageUi>>(messages);
return messagesUi;
}
I'm calling this method on a web service and the problem is: the first time I call the webservice (using the webservice console) it all runs perfectly. For example, if I pass '555' as the userId I get to this method with the correct value:
And in the Custom Resolver the value was correctly passed to the constructor:
The results returned are correct. The problem comes next. The second time I call the service, passing a different argument ('666' this time) the argument that gets to the constructor of the Custom Resolver is the old one ('555'). Here's what I mean:
Right before mapping the objects we can see that the value passed to the constructor was correct ('666'):
But when it gets to the constructor of the Resolver the value is wrong, and is the old one ('555'):
All subsequent calls to the service use the original value in the Custom Resolver constructor ('555'), independently of the value I pass to the service (also happens if I make the call from another browser). If I shut down the server and relaunch it I can pass a new parameter (that will be used in all other calls until I shut it down again).
Any idea on why this is happening?
It's happening because AutoMapper.CreateMap is a static method, and only needs to be called once. With the CreateMap code in your web method, you're trying to call it every time you call that method on your web service. Since the web server process stays alive between calls (unless you restart it, like you said) then the static mappings stay in place. Hence, the necessity of calling AutoMapper.Reset, as you said in your answer.
But it's recommended that you put your mapping creation in AppStart or Global or a static constructor or whatever, so you only call it once. There are ways to call Map that allow you to pass in values, so you don't need to try to finesse things with the constructor of your ValueResolver.
Here's an example using a ValueResolver (note the change to implementing IValueResolver instead of inheriting ValueResolver<TSource, TDestination>):
[Test]
public void ValueTranslator_ExtraMapParameters()
{
const int multiplier = 2;
ValueTranslator translator = new ValueTranslator();
Mapper.AssertConfigurationIsValid();
ValueSource source = new ValueSource { Value = 4 };
ValueDest dest = translator.Translate(source, multiplier);
Assert.That(dest.Value, Is.EqualTo(8));
source = new ValueSource { Value = 5 };
dest = translator.Translate(source, multiplier);
Assert.That(dest.Value, Is.EqualTo(10));
}
private class ValueTranslator
{
static ValueTranslator()
{
Mapper.CreateMap<ValueSource, ValueDest>()
.ForMember(dest => dest.Value, opt => opt.ResolveUsing<ValueResolver>().FromMember(src => src.Value));
}
public ValueDest Translate(ValueSource source, int multiplier)
{
return Mapper.Map<ValueDest>(source, opt => opt.Items.Add("multiplier", multiplier));
}
private class ValueResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
return source.New((int)source.Value * (int)source.Context.Options.Items["multiplier"]);
}
}
}
private class ValueSource { public int Value { get; set; } }
private class ValueDest { public int Value { get; set; } }
And here's an example using a TypeConverter:
[Test]
public void TypeTranslator_ExtraMapParameters()
{
const int multiplier = 3;
TypeTranslator translator = new TypeTranslator();
Mapper.AssertConfigurationIsValid();
TypeSource source = new TypeSource { Value = 10 };
TypeDest dest = translator.Translate(source, multiplier);
Assert.That(dest.Value, Is.EqualTo(30));
source = new TypeSource { Value = 15 };
dest = translator.Translate(source, multiplier);
Assert.That(dest.Value, Is.EqualTo(45));
}
private class TypeTranslator
{
static TypeTranslator()
{
Mapper.CreateMap<TypeSource, TypeDest>()
.ConvertUsing<TypeConverter>();
}
public TypeDest Translate(TypeSource source, int multiplier)
{
return Mapper.Map<TypeDest>(source, opt => opt.Items.Add("multiplier", multiplier));
}
private class TypeConverter : ITypeConverter<TypeSource, TypeDest>
{
public TypeDest Convert(ResolutionContext context)
{
TypeSource source = (TypeSource)context.SourceValue;
int multiplier = (int)context.Options.Items["multiplier"];
return new TypeDest { Value = source.Value * multiplier };
}
}
}
private class TypeSource { public int Value { get; set; } }
private class TypeDest { public int Value { get; set; } }
Answering myself: I was not using AutoMapper.Reset(). Once I did that everything started working properly.
Helpful reading: http://www.markhneedham.com/blog/2010/01/27/automapper-dont-forget-mapper-reset-at-the-start/

how to get javafx media metadata without listener

so I've been looking for this for a week now and reading though every problem similar but none seemed to ask the same problem as mine exactly(try reverse engineering other solution similar to what I want with no success.
explained caveman style: I'm trying to create list using Metadata.
I open with a multi dialog and select more than one mp3
I put the file in an ArrayList<File>
I loop though the files with an enhanced for loop and extract metadata using a media variable
The info for the metadata ( like "artist") is what i want to save in an ArrayList for example
the problem is that the listener only works way after the enhanced loop has finished which results in
ArrayList<String> having one object with nothing in it
here is a sample:
ArrayList<String> al;
String path;
public void open(){
files=chooser.showOpenMultipleDialog(new Stage());
for( File f:files){
path=f.getPath();
Media media = new Media("file:/"+path.replace("\\", "/").replace(" ", "%20"));
al= new ArrayList<String>();
media.getMetadata().addListener(new MapChangeListener<String, Object>() {
public void onChanged(Change<? extends String, ? extends Object> change) {
if (change.wasAdded()) {
if (change.getKey().equals("artist")) {
al.add((String) change.getValueAdded());
}
}
}
});
}//close for loop
//then i want to see the size of al like this
system.out.println(al.size());
//then it returns 1 no matter how much file i selected
//when i system out "al" i get an empty string
the other way to read a media source metadata with adding a listener is extract that information in the mediaplayer .setOnReady(); here is an example part of the java controller class
public class uiController implements Initializable {
#FXML private Label label;
#FXML private ListView<String> lv;
#FXML private AnchorPane root;
#FXML private Button button;
private ObservableList<String> ol= FXCollections.observableArrayList();
private List<File> selectedFiles;
private final Object obj= new Object();
#Override
public void initialize(URL url, ResourceBundle rb) {
assert button != null : "fx:id=\"button\" was not injected: check your FXML file 'ui.fxml'.";
assert label != null : "fx:id=\"label\" was not injected: check your FXML file 'ui.fxml'.";
assert lv != null : "fx:id=\"lv\" was not injected: check your FXML file 'ui.fxml'.";
assert root != null : "fx:id=\"root\" was not injected: check your FXML file 'ui.fxml'.";
// initialize your logic here: all #FXML variables will have been injected
lv.setItems(ol);
}
#FXML private void open(ActionEvent event) {
FileChooser.ExtensionFilter extention= new FileChooser.ExtensionFilter("Music Files", "*.mp3","*.m4a","*.aif","*.wav","*.m3u","*.m3u8");
FileChooser fc= new FileChooser();
fc.setInitialDirectory(new File(System.getenv("userprofile")));
fc.setTitle("Select File(s)");
fc.getExtensionFilters().add(extention);
selectedFiles =fc.showOpenMultipleDialog(root.getScene().getWindow());
if(selectedFiles != null &&!selectedFiles.isEmpty()){
listFiles();
}
}
/**
* Convert each fie selected to its URI
*/
private void listFiles(){
try {
for (File file : selectedFiles) {
readMetaData(file.toURI().toString());
synchronized(obj){
obj.wait(100);
}
}
} catch (InterruptedException ex) {
}
System.gc();
}
/**
* Read a Media source metadata
* Note: Sometimes the was unable to extract the metadata especially when
* i have selected large number of files reasons i don't known why
* #param mediaURI Media file URI
*/
private void readMetaData(String mediaURI){
final MediaPlayer mp= new MediaPlayer(new Media(mediaURI));
mp.setOnReady(new Runnable() {
#Override
public void run() {
String artistName=(String) mp.getMedia().getMetadata().get("artist");
ol.add(artistName);
synchronized(obj){//this is required since mp.setOnReady creates a new thread and our loopp in the main thread
obj.notify();// the loop has to wait unitl we are able to get the media metadata thats why use .wait() and .notify() to synce the two threads(main thread and MediaPlayer thread)
}
}
});
}
}
the few changes that have made is used an ObservableList to store the artist name from the metadata
in the code you will find this
synchronized(obj){
obj.wait(100);
}
I do this because the mediaplayer .setOnReady() creates a new thread and the loop is in the main application thread, The loop has to wait for some time before the other thread is created and we are able to extract the metadata, and in the .setOnReady() there is a
synchronized(obj){
obj.notify;
}
to wake up the main thread hence the loop is able to move to the next item
I admit that this may not be the best solution to do this but am welcomed to anyone who has any better way on how to read JavaFx media metadata from a list of files
The full Netbeans project can be found here https://docs.google.com/file/d/0BxDEmOcXqnCLSTFHbTVFcGIzT1E/edit?usp=sharing
plus have created a small MediaPlayer Application using JavaFX which expolits use of the metadata https://docs.google.com/file/d/0BxDEmOcXqnCLR1Z0VGN4ZlJkbUU/edit?usp=sharing
You can use the following function to retrieve the metadata for a given Media object:
public static void initializeMetaData(Media media) {
final Ref<Boolean> ready = new Ref<>(false);
MediaPlayer mediaPlayer = new MediaPlayer(media);
mediaPlayer.setOnReady(() -> {
synchronized (ready) {
ready.set(false);
ready.notify();
}
});
synchronized (ready) {
if (!ready.get()) {
try {
ready.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
However, do not call initializeMetaData from a JavaFX thread, otherwise the thread runs into a deadlock.
PS: It's really ridiculous that one has to build such a workaround. I hope that in future Media will provide an initialize() method which does this job.
My solution to that issue was this:
public class MediaListener implements MapChangeListener<String, Object>
{
public String title = null;
public String artist = null;
public String album = null;
private final Consumer<MediaListener> handler;
private boolean handled = false;
public MediaListener(Consumer<MediaListener> handler)
{
this.handler = handler;
}
#Override
public void onChanged(MapChangeListener.Change<? extends String, ?> ch)
{
if (ch.wasAdded())
{
String key = ch.getKey();
switch (key)
{
case "title":
title = (String) ch.getValueAdded();
break;
case "artist":
artist = (String) ch.getValueAdded();
break;
case "album":
album = (String) ch.getValueAdded();
break;
}
if (!handled && title != null && artist != null && album != null)
{
handler.accept(this);
handled = true;
}
}
}
}
It may not be the best way but it's way cleaner than creating a new MediaPlayer per file.
Example usage:
Media media = Util.createMedia(path);
media.getMetadata().addListener(new MediaListener((data) ->
{
// Use the data object to access the media
}));

Trying to understand the AsyncToken in Flex/Actionscript

I am trying to understand the way the AsyncToken works in actionscript. How can I call a remote service and ensure that a specific parameter is available in the result or fault event functions? I think it is the async functionality I want to use.
The following code will hopefully explain what I am trying to do. Feel free to modify the code block as your explanation.
Thanks.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
// Assume callBackCommand == "FOO";
// How can I pass in callBackCommand as a parameter to the result or fault events?
// How do I create an async token here?
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
remoteObject.test(data);
}
private function _handleTestResult( event:ResultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// How do I get the async token value?
// How can I get the value of callBackCommand in this code block?
}
An edit to make this question more clear:
Assume I make the following method call somewhere in my code:
testSerivceCall(personObject, "LoginCommand");
How do I get access to the actual string "LoginCommand" inside the _handleTestResult function block?
The reason I want to do this is because I want to dynamically call back certain functions and hand off the result data to specific commands that I know ahead of time when I am making the service call.
I am just having a time grokking the AsyncToken syntax and functionality.
I did not even need closures. I added a class as below which I called externally.
The call was like this:
public class MyClass
{
...
var adminServerRO:AdminServerRO = new AdminServerRO();
adminServerRO.testSerivceCall("FOO",cptyId);
}
public class AdminServerRO
{
private function extResult( event:ResultEvent, token:Object ) : void
{
//the token is now accessed from the paremeter
var tmp:String = "in here";
}
private function extFault( event:FaultEvent ) : void
{
var tmp:String = "in here";
}
public function testSerivceCall(callBackCommand:String, cptyId:String):void
{
var remoteObject:RemoteObject = new RemoteObject();
remoteObject.destination = "adminServer";
var token:AsyncToken = remoteObject.getCounterpartyLimitMonitorItemNode(cptyId);
token.addResponder(new AsyncResponder(extResult,extFault,cptyId));
}
}
While the accepted answer will accomplish what the original submitter wants it does not actually answer the question which was asked. An AsyncToken is created as a result of a remote method call and is accessible from the ResultEvent. Since AsyncToken is a dynamic class you can add whatever property to it that you want. The code below should demonstrate this:
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token:AsyncToken = remoteObject.test(data);
token.callBackCommand = callBackCommand;
}
private function _handleTestResult( event:ResultEvent ) : void
{
if (event.token.callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
//event.token.callBackCommand should be populated here too
}
If you want to access the properties used during the remote call (parameters to the call and/or AsycToken), you can make use of closures. Just define the result event handler inside the calling method as a closure. It can then access any variable in the calling function.
public function testSerivceCall(data:Object, callBackCommand:String):void
{
var _handleTestResult:Function = function( event:ResultEvent ) : void
{
// token is visible here now
if (callBackCommand == "FOO")
{
// do something related to "FOO"
}
else
{
// do something else with the result event
}
}
var remoteObject:RemoteObject;
remoteObject = new RemoteObject();
remoteObject.destination = "zend";
remoteObject.source = "MyService";
remoteObject.endpoint = "http://example.com/service";
remoteObject.test.addEventListener(ResultEvent.RESULT, _handleTestResult);
remoteObject.test.addEventListener(FaultEvent.FAULT, _handleTestFault);
var token = remoteObject.test(data);
}
If I'm reading your question correctly, you're trying to figure out how to access the actual data returned by the ResultEvent ?
If so, assuming you've made the call correctly and you've gotten data back in a format you're expecting:
private function _handleTestResult( event:ResultEvent ) : void
{
// you get the result from the result property on the event object
// edit: assuming the class Person exists with a property called name
// which has the value "John"
var person : Person = event.result as Person;
if (person.name == "John")
{
Alert.show("John: " + person.name);
}
else
{
Alert.show("Not John: " + person.name);
}
}
private function _handleTestFault( event:FaultEvent ) : void
{
// Maybe you know the type of the returned fault
var expectedFault : Object = event.fault as MyPredefinedType
if (expectedFault.myPredefinedTypesPredefinedMethod() == "BAR")
{
// something here
}
}
The ResultEvent has a property called result which will hold an instance of the object returned by the result (it might be the output of an XML file if using a web service, or a serialized object if using AMF, for example). This is what you want to access. Similarly, FaultEvent has a fault property that returns the fault information.
Edit: Changed code in _handleTestResult() in response to Gordon Potter's comment.