I'm working on a wp8-app that takes a photo and then takes you to the next screen to decide whether you like it or not.
The current approach was this:
private void ShutterButton_Click(object sender, RoutedEventArgs e)
{
if (cam != null)
{
try
{
cam.CaptureImage();
await Task.Delay(1500);
NavigateFront();
}
catch (Exception ex)
{
...
}
}
}
public void NavigateFront()
{
string naviString = "/confirmPicture.xaml?parameter=" + fileName.ToString();
_rootFrame.Navigate(new Uri(naviString, UriKind.Relative));
}
On my Lumia 520 it crashed sometimes. If I increase the wait-time to 2,5sec it works. But of course this should not be the way to do it.
If I catch the void cam_CaptureImageAvailable(object sender, Microsoft.Devices.ContentReadyEventArgs e)-Event and try to navigate after everything is done and all streams are closed I still get in a NavigateFailed-State and the app crashes.
My question is: is there any other useful event that ensures that all work is done and I can navigate without using static time-based values?
Navigation with a PhotoCamera is possible, just subscribe to its CaptureCompleted event handler
cam.CaptureCompleted += new EventHandler<CameraOperationCompletedEventArgs>(camera_CaptureCompleted);
and this would be the event
void camera_CaptureCompleted(object sender, CameraOperationCompletedEventArgs e)
{
try
{
Deployment.Current.Dispatcher.BeginInvoke(delegate()
{
try
{
cam.Dispose();
NavigationService.Navigate(new Uri("URI nething", UriKind.Relative));
}
catch (Exception)
{
MessageBox.Show("Problem occured!!");
}
});
}
catch
{
MessageBox.Show("Problem in camer_capturecompleted");
}
}
I did it in one of my apps targeting windows phone 7. Check if this works for you as well.
Related
I am using the TAPI 2.0 wrapper from JulMar (https://atapi.codeplex.com/) and I'm having trouble with it.
The Initialization
void initTAPI()
{
myTAPI = new TapiManager("GetCaller");
if (!myTAPI.Initialize())
{
MessageBox.Show("FAILED!");
}else
{
name = myTAPI.Lines[0].Name;
lineName = (myTAPI != null && myTAPI.Lines.Length > 0 ? name : string.Empty);
foreach(TapiLine line in myTAPI.Lines)
{
line.NewCall += this.OnNewCall;
line.Ringing += this.OnRinging;
line.CallStateChanged += this.OnCallState;
line.CallInfoChanged += this.OnCallInfo;
}
MessageBox.Show(lineName);
}
}
So I get the lineName. When I now dial a number through the program, it fires
OnCallState
private void OnCallState(object sender, CallStateEventArgs e)
{
if (InvokeRequired == true)
{
this.BeginInvoke(new EventHandler<CallStateEventArgs>(this.OnCallState), new object[] { sender, e });
return;
}
label1.Text = "Outgoing Call...";
}
But what I actually want to do is to get the number of an incoming call, but OnCallInfo does not get fired.
OnCallInfo
private void OnCallInfo(object sender, CallInfoChangeEventArgs e)
{
if (InvokeRequired == true)
{
this.BeginInvoke(new EventHandler<CallInfoChangeEventArgs>(this.OnCallInfo), new object[] { sender, e });
return;
}
label1.Text = "Incoming Call...";
}
It says somehwere, that it only works with x86, so I changed the target but still no success.
PS: I have a call manager (ProCall) installed on the same machine, that tells me when someone calls, so I should be able to get the info in c# as well?
Here is the whole code if someone is interested: http://pastebin.com/Q5W5iGun
Depending on TSP, you may get call info messages, but TAPI does not force the driver to do this. So some TSP make you get the info yourself. In the Win32 API this is done via lineGetCallInfo.
After a quick look in this atapi wrapper, this happens in the GatherCallInfo method of the TapiCall class. However I can see no way to trigger this manually in this wrapper. You would need to modify the atapi source to make this a public method.
You can use example from TAPI which do the same. The only difference is new line.Monitor() method
foreach (TapiLine line in tapiManager.Lines)
{
try
{
line.NewCall += OnNewCall;
line.CallStateChanged += OnCallStateChanged;
line.CallInfoChanged += OnCallInfoChanged;
line.Monitor();
}
catch (TapiException ex)
{
LogError(ex.Message);
}
}
For further reading read this https://atapi.codeplex.com/SourceControl/latest#Atapi/trunk/source/test/TcMon/TapiMonitorForm.cs
I have a problem that I wrote an application that would iterate through files and add +1 to the integer each file, until it reaches a specific file name. The problem is probably because .Net does not access the native file system directly, it fills up collections, but in my case it would take years, believe me, I have 260 000 files in the target folder. The iteration does not even reach the second file. The thread just totally freezes, no errors, no exceptions. So is there any way to get a direct access to the Native File System without any useless collection filling ups?
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
try
{
foreach (string file in Directory.GetFiles("\\\\Mypcname-PC\\vxheaven\\malware"))
{
count++;
label1.Text = Convert.ToString(count);
if (file.Contains(textBox1.Text))
{
label1.Text = Convert.ToString(count) + " reached the file";
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
btw. Sorry for my bad english
Regards
Because you are doing all the work on the UI thread it can't refresh while it is working. You need to do the work on a background thread then update the UI in a thread safe way. Also switching to Directory.EnumerateFiles will make it faster to read the first file so it does not need to store all the records in to an array. Lastly I changed ex.Message to ex.ToString(), it will display much more useful information that way.
private async void button1_Click(object sender, EventArgs e)
{
try
{
var text = textBox1.Text;
var progress = new Progress<string>((x) => label1.Text = x);
await Task.Run(() => DoWork(progress, text));
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private void DoWork(IProgress<string> progress, string text)
{
foreach (string file in Directory.EnumerateFiles("\\\\Mypcname-PC\\vxheaven\\malware"))
{
count++;
progress.Report(Convert.ToString(count));
if (file.Contains(text))
{
progress.Report(Convert.ToString(count) + " reached the file");
break;
}
}
}
(Code was written in a web browser from memory so there may be errors)
Seems like you are using a potentially very time-consuming loop without ever processing the Windows message queue, therefore your application may APPEAR to be frozen, while it's probably just busy doing what you instructed it to do in the loop. Try this:
private void button1_Click(object sender, EventArgs e)
{
try
{
foreach (string file in Directory.GetFiles("\\\\Mypcname-PC\\vxheaven\\malware"))
{
count++;
label1.Text = Convert.ToString(count);
Application.DoEvents();
if (file.Contains(textBox1.Text))
{
label1.Text = Convert.ToString(count) + " reached the file";
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
I already have the saving part down and I know it works, but when I click load button, it will not display anything that I have saved from the text boxes that go to the saying.txt file
using System;
using System.IO;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Grades : Form
{
private StreamWriter fil;
public Grades()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
try
{
fil = new StreamWriter("saying.txt"); //This is the txt file
}
catch (DirectoryNotFoundException exc)
{
lstBxDisplay.Text = "Nothing " +
exc.Message;
}
catch (System.IO.IOException exc)
{
lstBxDisplay.Text = exc.Message;
}
}
// saving the files to the saying.txt
private void btnSaveAs_Click(object sender, EventArgs e)
{
try
{
fil.WriteLine(txtBxLastName.Text);
txtBxLastName.Text = "";
txtBxLastName.Focus();
}
catch (System.IO.IOException exc)
{
lstBxDisplay.Text = exc.Message;
}
}
// next is the load button to load the files into the list/display box
private void btnLoad_Click(object sender, EventArgs e)
{
string inValue;
try
{
using (StreamReader infil =
new StreamReader("saying.txt"))
{
inValue = infil.ReadLine();
while (inValue != null)
{
inValue = infil.ReadLine();
if (inValue != null)
this.lstBxDisplay.Items.Add(inValue);
} // end of while
} // end of using
}
catch (System.IO.IOException exc)
{
lstBxDisplay.Text = exc.Message;
}
}
private void Grades_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
fil.Close();
}
catch
{
}
}
}
}
any reason why it is not loading into the list box? I have tried both label and text box to display the message and neither of them work. I debugged the program and it is executing fine
You have multiple issues here but I will point out two main issues that will get your code working.
You are not closing the stream when you try to save. If the stream stays open, you will never be able to read the values when you try to "Load" the file. You need to call fil.Close(); at the end of your btnSaveAs_Click method.
This is how the save should read.
fil.WriteLine(txtBxLastName.Text);
txtBxLastName.Text = "";
txtBxLastName.Focus();
fil.Close();
You're skipping the first line of the file in your "Load" method. You call infil.ReadLine(); then in the loop, you call it again before you add it to your listbox. You need to move your second ReadLine(). If you are only ever writing a single line to the file, your existing code will skip that first line and try to read it again which will be null (no second line). So, it will never add anything to your listbox.
This is how the reads should be ordered.
using (StreamReader infil = new StreamReader("saying.txt"))
{
inValue = infil.ReadLine();
while (inValue != null)
{
this.lstBxDisplay.Items.Add(inValue);
inValue = infil.ReadLine();
} // end of while
} // end of using
Those changes will get you working. Now to point out, you are going about reading and writing to a file all wrong. You should not be opening a stream in your form load and waiting for button clicks to be writing/reading from that stream. Unless you have a very good reason to do what you are doing, you should be opening your stream, performing the read/write operation, and closing your stream right away. For a simple file IO, I would even suggest using a different mechanism. Look at the MSDN for the System.IO.File Class for easier methods to read lines or write lines to a file.
I am using Image and MediaElement in wpf project, where I show Images and Videos from file system. I have few timers, which load files to Image/MediaElement controls. Everything works for 4-5 hours, but then MediaElement Video file freezes and MediaEnded event does not occur. I restart the application, it runs without any problem, but after some hours this problem occurs again.
My WPF XAML code:
<Grid Name="MainGrid">
<Image HorizontalAlignment="Center" VerticalAlignment="Center" Name="MainImage" Stretch="Fill" />
<MediaElement MediaEnded="MediaEnded" MediaOpened="MediaOpened" LoadedBehavior="Manual" HorizontalAlignment="Center" Name="VideoControl" VerticalAlignment="Center"
Stretch="Fill" UnloadedBehavior="Manual"/>
</Grid>
C# code:
public partial class ImageView
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static String _advCheckGuid;
private List<String> _FolderNames;
private int _FolderIndex = 0;
private MainWindow _MainWindow;
private List<String> _PathList;
private List<String> _CheckPathList;
private int _Index;
private BitmapImage _BitmapImage;
private volatile bool _Running = true;
private Backend _Backend;
private ApplicationDeployment _UpdateCheck;
// Threads
private Timer _ImageTimer;
private Timer _UpdateTimer;
private Timer _FolderClearTimer;
private Timer _CheckApplicationUpdateTimer;
private Thread _TerminationThread;
public ImageView()
{
InitializeComponent();
_PathList = new List<string>();
_CheckPathList = new List<string>();
_Index = 0;
}
private void ViewPageLoaded(Object sender, EventArgs e)
{
_FolderNames = new List<string> { Constants.AdsFolderFirst,
Constants.AdsFolderSecond };
_Backend = new Backend();
_MainWindow = (MainWindow)Window.GetWindow(this);
_ImageTimer = new Timer(Constants.DefaultImageTimer);
_ImageTimer.Elapsed += ChangeImageSource;
_ImageTimer.Start();
}
private void ChangeImageSource(object sender, System.Timers.ElapsedEventArgs e)
{
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Normal, new Action(
delegate()
{
try
{
if (MainImage != null && MainImage.Source != null)
{
MainImage.Source = null;
}
if (VideoControl != null && VideoControl.Source != null)
{
VideoControl.Stop();
VideoControl.Source = null;
}
if (_Index >= _PathList.Count)
{
_Index = 0;
}
if (_PathList.ElementAt(_Index) != null)
{
Log.Info(String.Format("Start [ChangeImageSource]. Element: {0}, Index: {1}", _PathList.ElementAt(_Index), _Index));
try
{
_ImageTimer.Stop();
String[] checkExt = _PathList.ElementAt(_Index).Split('.');
String ext = checkExt[checkExt.Length - 1];
if (ext.Equals("jpg", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("jpeg", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("png", StringComparison.CurrentCultureIgnoreCase))
{
_ImageTimer.Interval = Constants.NormalImageTimer;
ShowImage(_PathList.ElementAt(_Index));
}
else if (ext.Equals("mp4", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("3gp", StringComparison.CurrentCultureIgnoreCase))
{
_ImageTimer.Interval = Constants.VideoDefaultTimer;
PlayQueue(_PathList.ElementAt(_Index));
}
_ImageTimer.Start();
_Index++;
}
catch (Exception exception)
{
Log.ErrorException(exception.Message, exception);
}
}
}
catch (Exception exception)
{
Log.ErrorException(exception.Message, exception);
}
}));
}
private void ShowImage(String fileName)
{
try
{
if (!String.IsNullOrEmpty(fileName))
{
_BitmapImage = LoadImage(fileName);
MainImage.Source = _BitmapImage;
}
}
catch (Exception e)
{
Log.ErrorException(e.Message, e);
}
}
private void PlayQueue(String fileName)
{
try
{
if (!String.IsNullOrEmpty(fileName))
{
VideoControl.LoadedBehavior = MediaState.Play;
VideoControl.Source = new Uri(fileName, UriKind.Absolute);
}
}
catch (Exception e)
{
Log.ErrorException(e.Message, e);
}
}
private void MediaEnded(object sender, EventArgs e)
{
try
{
if (MainImage != null && MainImage.Source != null)
{
MainImage.Source = null;
}
if (VideoControl != null && VideoControl.Source != null)
{
VideoControl.Stop();
VideoControl.Source = null;
}
if (_Index >= _PathList.Count)
{
_Index = 0;
}
if (_PathList.ElementAt(_Index) != null)
{
Log.Info(String.Format("Start [MediaEnded oper]. Element: {0}, Index: {1}", _PathList.ElementAt(_Index), _Index));
try
{
_ImageTimer.Stop();
String[] checkExt = _PathList.ElementAt(_Index).Split('.');
String ext = checkExt[checkExt.Length - 1];
if (ext.Equals("jpg", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("jpeg", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("png", StringComparison.CurrentCultureIgnoreCase))
{
_ImageTimer.Interval = Constants.NormalImageTimer;
ShowImage(_PathList.ElementAt(_Index));
}
else if (ext.Equals("mp4", StringComparison.CurrentCultureIgnoreCase) ||
ext.Equals("3gp", StringComparison.CurrentCultureIgnoreCase))
{
_ImageTimer.Interval = Constants.VideoDefaultTimer;
PlayQueue(_PathList.ElementAt(_Index));
}
_ImageTimer.Start();
_Index++;
}
catch (Exception exception)
{
Log.ErrorException(exception.Message, exception);
}
}
}
catch (Exception exception)
{
Log.ErrorException(exception.Message, exception);
}
}
private void MediaOpened(object sender, EventArgs e)
{
}
private BitmapImage LoadImage(string myImageFile)
{
BitmapImage myRetVal = null;
if (!String.IsNullOrEmpty(myImageFile))
{
var image = new BitmapImage();
try
{
using (FileStream stream = File.OpenRead(myImageFile))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
}
catch (Exception exception)
{
Log.ErrorException(exception.Message, exception);
}
myRetVal = image;
}
return myRetVal;
}
I googled it and found that this was WPF graphic issue related to software rendering. The issue is solved by adding this piece of code into the ViewPageLoaded method.
try
{
var hwndSource = PresentationSource.FromVisual(this) as HwndSource;
var hwndTarget = hwndSource.CompositionTarget;
hwndTarget.RenderMode = RenderMode.SoftwareOnly;
}
catch (Exception ex)
{
Log.ErrorException(ex.Message, ex);
}
It helped me to solve the problem. Hope it will help you too.
Got the answer from here. Thanks to #detale for the solution
This is a complicated problem.. I'll try to explain it in depth. (and yes I have a solution for you)
lets start with What MediaElement should be capable of doing? no.. really!
Its a wildcard right? meaning that what ever you throw on it - needs to be played: Videos, Pictures, Animated Gifs, Music.. Ok..
Now.. Each of those categories has multiple Formats (or standards).. Gif,Png.. Wmv,Mp4...
And so, each of those files we use was created by some other editor
(which has a player that can play it implemented inside - that's for sure..)
It seems that most of the companies cut expenses - they don't always (usually that is..) implement a standard in full.. so what we get as result files are not always 1:1 to the standard.
So what is perfect file format for one player can be considered as too corrupted for another player.
And while the commercial/advanced players are designed to be tolerant to corruptions and "flavors" of a file written in some standard - MediaElement - well.. is more simplistic and perhaps it is too simplistic compared to what you may throw at it to play.
So when it stump into that type of problem - yes.. it may freeze and will not report - and that is something that I can blame Microsoft in full - and why? because it is an acceptable defect to freeze but it is not acceptable (and extremely irresponsible!) to ignore it and not notify the program which using the MediaElement that it frozen or encountered a serious presentation error..
But as I said, this is Microsoft problem and definitely not your fault.
So what are the solutions?
You may try to say to yourself "Fine - I'll just get another component to play videos or use a 3rd party plug-in..", But no my friend, Doing that will not really solve your problem as you do not know if what you about to put in replace would not suffer from the exact same problem..
So the only option you are left with is to create your own "custom" standard - relax, I do not mean you need to develop a new standard - I just mean you need to create a standard tactic to make sure that what you going to throw on the MediaElement will be played with no freezes..
So, If your app going to play videos which are being used as resources - You may want to use in example the latest version of AnyVideoConverter to convert all your videos to mp4. For me it worked rather well, videos which were freezing in wmv converted to mp4 and are now tolerated by the MediaElement very smoothly. it is not the MP4 that did the trick but the conversion itself - I believe that ANV creates a "modernized" video file of any of the standards you may use for your files.
However, If your videos are dynamic/uploaded to your app during runtime or something like that - you will have to make sure to pass any video your app about to run through what you choose as "standardizer" before you can actually throw them at the MediaElement.
By the way, Browsers suffer from the same problem occasionally.
I just hope all this may sort the problem for anyone else who encountered it.
You are creating lots of BitmapImage instances, there is memory leak in BitmapImage class, The BitmapImage keeps a reference to the source stream (presumably so that you can read the StreamSource property at any time), so it keeps the MemoryStream object alive. This is causing Memory out exception. Read this, he has created a nice wrapper for stream, It worked for me.
He created an Instance of stream in wrapperclass which get disposed when you call dispose method of wrapper and BitmapImage.Source only has empty wrapper class which doesn't have any reference to original stream.
I would suggest registering for the MediaElement.MediaFailed event. See if it returns anything to you.
However, as others have mentioned this sounds like a memory issue. You can use the WPF Performance Suite or even just the task manager to confirm this. Watch for a very gradual increase in memory usage.
As Shivam cv has mentioned, it could be the BitmapImage is the leak. Try commenting it out of your solution and see if that fixes the issue.
C# 2008
I have developed the class below. I have to get the balance from the web server. Once that is done it will call back into my main app with the result.
However, sometime the web server fails for some unknown reason. Could be high volume of traffic or something else. However, I haven't implemented any exception handling in my class. As the app that uses this handles the exception.
However, the client has confirmed that when the web server does fail it displays a unhandled exception dialog box. Then they have to click continue to keep using my application.
So below I am not sure if I should implement the exception handling in my class. However, I am confused as to why the exception was not caught in my app that as below.
Many thanks for any suggestions, or if you see anything else wrong,
private void OnGetBalanceCompleted(object sender, SIPPhoneLibraryEventArgs e)
{
try
{
//If the balance starts with 'null' there has been an error trying to get the balance.
if (e.Balance.StartsWith("null"))
{
statusDisplay1.CurrentBalance = CATWinSIP_MsgStrings.BalanceError;
}
else
{
// Display the current balance and round to 2 decimal places.
statusDisplay1.CurrentBalance = Math.Round(Convert.ToDecimal(e.Balance), 2).ToString();
//If the balance is zero display in the status message
if (decimal.Parse(e.Balance) == 0)
{
this.statusDisplay1.CallStatus = "Zero Balance";
}
}
//Remove the event as no longer needed
siplibrary.GetBalanceCompletedEvent -= new EventHandler<SIPPhoneLibraryEventArgs>(OnGetBalanceCompleted);
}
catch (WebException ex)
{
MessageBox.Show(ex.Message);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
//Control library for all importing functions
public class Balance : IDisposable
{
//Constructor
WebClient wc;
public Balance()
{
using (wc = new WebClient())
{
//Create event handler for the progress changed and download completed events
wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
}
}
~Balance()
{
this.Dispose(false);
}
//Event handler and the method that handlers the event
public EventHandler<SIPPhoneLibraryEventArgs> GetBalanceCompletedEvent;
//The method that raises the event
public void OnGetBalanceCompleted(SIPPhoneLibraryEventArgs e)
{
if (GetBalanceCompletedEvent != null)
{
GetBalanceCompletedEvent(this, e);
}
}
//Get the current balance for the user that is logged in.
//If the balance returned from the server is NULL display error to the user.
//Null could occur if the DB has been stopped or the server is down.
public void GetBalance(string sipUsername)
{
//Remove the underscore ( _ ) from the username, as this is not needed to get the balance.
sipUsername = sipUsername.Remove(0, 1);
string strURL = string.Format("http://xxx.xxx.xx.xx:xx/voipbilling/servlet/advcomm.voipbilling.GetBalance?CustomerID={0}", sipUsername);
//Download only when the webclient is not busy.
if (!wc.IsBusy)
{
// Sleep for 1/2 second to give the server time to update the balance.
System.Threading.Thread.Sleep(500);
// Download the current balance.
wc.DownloadStringAsync(new Uri(strURL));
}
else
{
System.Windows.Forms.MessageBox.Show("Busy please try again");
}
}
//return and display the balance after the download has fully completed
void wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
//Pass the result to the event handler
this.OnGetBalanceCompleted(new SIPPhoneLibraryEventArgs(e.Result));
}
//Progress state of balance.
void wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
//Write the details to the screen.
Console.WriteLine(e.TotalBytesToReceive);
Console.WriteLine(e.BytesReceived);
Console.WriteLine(e.ProgressPercentage);
}
//Dispose of the balance object
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
//Remove the event handlers
private bool isDisposed = false;
private void Dispose(bool disposing)
{
if (!this.isDisposed)
{
if (disposing)
{
wc.DownloadProgressChanged -= new DownloadProgressChangedEventHandler(wc_DownloadProgressChanged);
wc.DownloadStringCompleted -= new DownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
wc.Dispose();
}
isDisposed = true;
}
}
}
It seems that you are catching the exception on the OnGetBalanceCompleted event only, instead on the process of fetching the balance.
When there is any error on the fetching, the OnGetBalanceCompleted is not even called, that's why your exception handler is not called.
There is more information in the exception than just its Message property. You are throwing all of that information away by only displaying the Message property. Use ex.ToString() instead.
Is the code you posted part of the user interface? If not, then it has no business knowing anything about the user interface. In particular, it should not be using MessageBox.Show.
I'd remove all the UI stuff, and instead raise an event. The caller would listen to the event and do any UI work.