I am working on WPF application running on .NET 6. Recently I encountered really strange behaviour with MediaElement control.
I have the backend service able to stream files, for instance videos. I want to embed a video player within the frontend (WPF) application with the streaming capability. I have already achieved this with libvlcsharp although it doesn't meet all the requirements (mainly performance) and is being left out as side/additional feature. I want to be able to stream multiple videos at the same time (in the same window as well) similar to youtube/instagram reels. For that purpose I decided to go with MediaElement control. I succesfully embeded multiple MediaElements but there are some issues.
I have confirmed that MediaElement doesn't work properly while on low bandwidth connection. While I am on my optic fiber 900+ Mbps connection everything works fine, the videos are being streamed with no issues. While on lower bandwidth (tested on two cellular networks with <20 Mbps) the Player is not loading/buffering/starting at all.
I could not find any related documentation.
I believe it is not related to the file format and file size as I tested the same files on both networks. Files ranging from a few KB to a few GB.
User control:
<UserControl x:Class="Peernet.Browser.WPF.Controls.EmbededPluginsControl">
<Grid>
<MediaElement Grid.Row="0" Name="Player" LoadedBehavior="Manual" UnloadedBehavior="Stop" Stretch="Fill" />
</Grid>
</UserControl>
Code behind:
public partial class EmbededPluginsControl : UserControl
{
public static readonly DependencyProperty FileProperty =
DependencyProperty.Register("File", typeof(ApiFile),
typeof(EmbededPluginsControl), null);
public EmbededPluginsControl()
{
InitializeComponent();
Loaded += EmbededPluginsControl_Loaded;
}
private void EmbededPluginsControl_Loaded(object sender, RoutedEventArgs e)
{
var settings = App.ServiceProvider.GetService(typeof(ISettingsManager)) as ISettingsManager;
Player.Source = GetFileSource(settings, File);
}
public ApiFile File
{
get => (ApiFile)GetValue(FileProperty);
set => SetValue(FileProperty, value);
}
public static Uri GetFileSource(ISettingsManager settingsManager, ApiFile file)
{
var parameters = new Dictionary<string, string>
{
["hash"] = Convert.ToHexString(file.Hash),
["node"] = Convert.ToHexString(file.NodeId),
["format"] = "14",
["k"] = settingsManager.ApiKey
};
var uriBase = $"{settingsManager.ApiUrl}/file/view";
var requestMessage = HttpHelper.PrepareMessage(uriBase, HttpMethod.Get, parameters, null);
return requestMessage.RequestUri;
}
}
By default all Players are stopped, there is no issue such as that I try to run all at once and it is killing the network or something. There are buttons dedicated for Player as simple as:
<Image x:Name="PlayButton" Height="20" Width="20" MouseDown="OnMouseDownPlayMedia" Margin="5">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{StaticResource PlayImage}" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Source" Value="{StaticResource PlayImage_MouseOver}" />
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
and
void OnMouseDownPlayMedia(object sender, MouseButtonEventArgs args)
{
Player.Play();
}
I also don't manipulate programatically the Player stream, buffering, etc.
Do I have to reset the stream while connection drops or something? I am running out of ideas.
Related
In dotnet's Avalonia-UI framework.
I'm using a dark UI and I managed to make everything dark as per this example but one thing: the window's System top bar in Windows OS.
I have seen in this issue in github that I can set the property HasSystemDecorations="false" to make it go away, but then I would have to implement myself the top bar with the drag functionality, title, close, maximize, minimize, etc, which is a pain when all I want is to change the background color.
What would be the easier way to make the window top bar change to a dark background color?
If the only way is using HasSystemDecorations then what would be the minimal example to implement the dark top bar with the common funcionality to close/minimize/maximize/drag?
Yes, you have to set HasSystemDecorations="false" and implement your own title bar. I have a basic template on Github for how to do this using version 0.10 and fluent theme.
It is actually quite easy, because Avalonia provides a lot of convenience methods for achieving that.
Overview:
Set
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
for the Window and then implement a titlebar. For example the close button could look something like this:
<Button Width="46"
VerticalAlignment="Stretch"
BorderThickness="0"
Name="CloseButton"
ToolTip.Tip="Close">
<Button.Resources>
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
</Button.Resources>
<Button.Styles>
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Red"/>
</Style>
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button:pointerover > Path">
<Setter Property="Fill" Value="White"/>
</Style>
<Style Selector="Button:not(:pointerover) > Path">
<Setter Property="Fill" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
</Button.Styles>
<Path Margin="10,0,10,0"
Stretch="Uniform"
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"></Path>
</Button>
If you set IsHitTestVisible="False" on a control, the window below can be dragged in that area. So wrap your whole titlebar in for example a DockPanel:
<DockPanel Background="Black"
IsHitTestVisible="False"
Name="TitleBarBackground"></DockPanel>
Now you obviously still need to mimic the behaviour of the buttons. This can be done in principal like that (again for a concrete example check out the Github repo above):
minimizeButton = this.FindControl<Button>("MinimizeButton");
maximizeButton = this.FindControl<Button>("MaximizeButton");
maximizeIcon = this.FindControl<Path>("MaximizeIcon");
maximizeToolTip = this.FindControl<ToolTip>("MaximizeToolTip");
closeButton = this.FindControl<Button>("CloseButton");
windowIcon = this.FindControl<Image>("WindowIcon");
minimizeButton.Click += MinimizeWindow;
maximizeButton.Click += MaximizeWindow;
closeButton.Click += CloseWindow;
windowIcon.DoubleTapped += CloseWindow;
private void CloseWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.Close();
}
private void MaximizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
if (hostWindow.WindowState == WindowState.Normal)
{
hostWindow.WindowState = WindowState.Maximized;
}
else
{
hostWindow.WindowState = WindowState.Normal;
}
}
private void MinimizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.WindowState = WindowState.Minimized;
}
Now the last step is that you need to change the icon of the maximize button depending on the window state. For example if you drag a maximized window, it will automatically become restored down and the icon of the maximize button needs to change. Therefore you need to subscribe to the window state of your host window, which can be done by overriding the Window.HandleWindowStateChanged method or by doing something like this:
private async void SubscribeToWindowState()
{
Window hostWindow = (Window)this.VisualRoot;
while (hostWindow == null)
{
hostWindow = (Window)this.VisualRoot;
await Task.Delay(50);
}
hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
{
hostWindow.Padding = hostWindow.OffScreenMargin;
if (s != WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z");
maximizeToolTip.Content = "Maximize";
}
if (s == WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 1638h-410v410h-1638v-1638h410v-410h1638v1638zm-614-1024h-1229v1229h1229v-1229zm409-409h-1229v205h1024v1024h205v-1229z");
maximizeToolTip.Content = "Restore Down";
}
});
}
Actually in the snippet above there is one more detail, which needs some attention. At least on windows, a maximized window is actually bigger than the screen. If you dont want your content to go out of the screens' bounds, you need to add a margin to your main control inside the window. Therefore the Padding of the hostWindow is changed accordingly.
Avalonia provides an IWindowImpl.OffScreenMargin property that describes the margin around the window that is offscreen.
You can directly bind to this property in the window's .axml
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
Padding="{Binding $self.OffScreenMargin}">
There is a way without having to create your own minimize/maximize/close buttons (I only tested it on Windows).
In your MainWindow.axaml:
<Window xmlns="https://github.com/avaloniaui"
...
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"/>
<Grid RowDefinitions="30,*">
<!-- Title bar -->
<Grid ColumnDefinitions="Auto,*" IsHitTestVisible="False" Background="Black">
<Image Grid.Column="0" VerticalAlignment="Center" Source="/Assets/YOUR-PATH-TO-YOUR-APP-ICON-IMAGE" Width="18" Margin="12,0,12,0" ></Image>
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="12" >YOUR-APPLICATION-TITLE-HERE</TextBlock>
</Grid>
<!-- Window content -->
<Your-User-Content-Here Grid.Row="1" Background="#222222" />
</Grid>
Here is the example in the AvaloniaUI documentation.
Here is an example in a real project with a black system bar.
I am attempting to create Coded UI tests on a WPF application. I am using Visual Studio 2019 to create / run the tests.
I am running into a strange behavior where a WPF button that was collapsed at startup (but is later made visible/enabled) is not showing any child nodes using any of the FindXXX methods available to the AutomationElement object associated with the button. Other buttons that were not collapsed do not seem to have this problem. I should note that the reason I am expecting child nodes for this WPF button is that in the XAML it is defined similar to the following:
<Button x:Name="ButtonStop" Grid.Row="0" Grid.Column="2" Command="{Binding TheVm.StopCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource ButtonStyleA}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding TheVm.DisplayButton}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<StackPanel Style="{StaticResource ControlsStackPanelStyle}">
<Image Source="pack://application:,,,/Assets/icon1.png" Style="{StaticResource ControlsButtonImageStyle}"/>
<ContentPresenter Content="Stop" Style="{StaticResource ControlsButtonTextStyle}"/>
</StackPanel>
</Button>
Using the INSPECT.EXE application I can see the child nodes of this button correctly, but when I traverse the AutomationElements I have access to they are missing.
The test code I am using to check the human-readable text is:
// Wait for 'Stop' button to become enabled, and verify correct text
uIButtonStopButton.WaitForControlEnabled();
var displayText = (!uIButtonStopButton.DisplayText.Equals(""))
? uIButtonStopButton.DisplayText
: GetFirstNodeText(uIButtonStopButton.NativeElement as AutomationElement;
Assert.AreEqual("Stop", displayText, "Stop button doesn\'t have correct text.");
Where the GetFirstNodeText method is as follows:
private static string GetFirstNodeText(AutomationElement automationElement)
{
if (automationElement != null)
{
// Get first AutomationElement node that is a 'text' control-type
var textEl = automationElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "text"));
if (textEl != null) return textEl.Current.Name;
}
return "";
}
An additional (interesting) piece of information: I tried a similar test using Appium/WinAppDriver and had an almost identical experience - no child nodes on the formerly collapsed button.
What could be causing this and do you have any advice for this?
To verify your are working with up-to-date AutomationElement objects, be sure to check this question about refreshing your controls.
But since you mentioned having a almost identical problem using WinAppDriver, I rather think the problem will be with the application under test.
If you have access to the source code / developers working on that code, please take a closer look to the code/xaml involving this button and its children. The problem will most likely be found there.
As I previously said in this question, I'm new at Xamarin.Forms and I'm developing a cross-platform web browser with Microsoft Visual Studio 2017 version 15.5.4. I'm debugging on an Android 5.1 smartphone.
In my layout there's a WebView and two Buttons to go back/forward.
<Button Image="backarrowdisabled.png"
Grid.Row="1"
Grid.Column="0"
x:Name="backButton"
IsEnabled="False"
Clicked="previousPage">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding CanGoBack, Source={Reference appWebView}}"
Value="False">
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Image" Value="backarrowdisabled.png" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding CanGoBack, Source={Reference appWebView}}"
Value="True">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="Image" Value="backarrow.png" />
</DataTrigger>
</Button.Triggers>
</Button>
<Button Image="nextarrowdisabled.png"
Grid.Row="1"
Grid.Column="5"
x:Name="nextButton"
IsEnabled="False"
Clicked="nextPage">
/* triggers */
</Button>
<WebView Grid.Row="3"
Grid.Column="0"
Grid.ColumnSpan="6"
x:Name="appWebView"
Source="https://www.google.it/"
Navigating="onPageLoading"
Navigated="onPageLoaded"/>
As you can see there are two methods to manage Navigating and Navigated events, which are
private void onPageLoading(object sender, WebNavigatingEventArgs e)
{
URLEntry.Text = e.Url; //Entry where I can see the page URL
//other code
}
private void onPageLoaded(object sender, EventArgs e)
{
//some code
}
When I click on a link or something like that in my WebView everything works correctly, but when I have to go back/forward in my history the Entry doesn't update the URL to the previous/next one. It seems it doesn't even call the onPageLoading method, but the WebView loads the page.
These are the methods called by the Buttons
private void previousPage (object sender, EventArgs e)
{
appWebView.GoBack();
}
private void nextPage (object sender, EventArgs e)
{
appWebView.GoForward();
}
Is it a WebView bug or am I doing something wrong?
Edit: I think the pages are cached, is there a way to make it not do it?
One thing you can do, is to create another event handler for the WebView's PropertyChanged or PropertyChanging event. There, you can check to see if the Source property was changed; If so, you can update the TextView with the new source. (I'm not sure exactly, but it seems like whe navigating back or forward, the Navigating event does not get called, possibly because the page is already cached?)
See here: https://developer.xamarin.com/api/member/Xamarin.Forms.WebView.OnPropertyChanged/p/System.String/
So your code can look like
<WebView x:Name="MyWebView" PropertyChanged="OnWebViewPropertyChanged" />
and then your code behind:
private void OnWebViewPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == WebView.SourceProperty.PropertyName)
{
URLEntry.Text = MyWebView.Source.ToString(); // May need to check this
}
}
I'm using Xamarin.Forms in a PCL with the XAML pages. The only way I figured out to style my controls is to use an inline syntax.
<Button Text="Inquiry" TextColor="Blue" />
I would prefer to use a structure like this one:
<Page.Resources>
<Style TargetType="Button">
<Setter Property="BorderThickness" Value="5" />
<Setter Property="Foreground" Value="Blue" />
</Style>
</Page.Resources>
(http://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh465381.aspx)
However, the Style element is not (yet) supported.
Has anyone succeeded in separating the layout from the contents?
FYI: I've also posted this question in Xamarin forums, so anyone who got here by google might want to also check out this page: http://forums.xamarin.com/discussion/19287/styling-of-xamarin-xaml#latest
Style is not that hard [citation needed]. You can implement your own, as I just did for the purpose of this answer.
Here's what the Xaml will look like:
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:YourNS;assembly=YourAssembly">
<ContentPage.Resources>
<ResourceDictionary>
<local:Style x:Key="buttonStyle">
<local:Setter Property="BorderWidth" Value="5"/>
</local:Style>
</ResourceDictionary>
</ContentPage.Resources>
<Button Text="Foo" local:Style.Style="{StaticResource buttonStyle}" x:Name="button"/>
</ContentPage>
the supporting code will look like:
namespace YourNS
{
public class Setter {
public string Property { get; set; }
public string Value { get; set; }
}
[ContentProperty ("Children")]
public class Style
{
public Style ()
{
Children = new List<Setter> ();
}
public IList<Setter> Children { get; private set; }
public static readonly BindableProperty StyleProperty =
BindableProperty.CreateAttached<Style, Style> (bindable => GetStyle (bindable), default(Style),
propertyChanged: (bindable, oldvalue, newvalue)=>{
foreach (var setter in newvalue.Children) {
var pinfo = bindable.GetType().GetRuntimeProperty (setter.Property);
pinfo.SetMethod.Invoke (bindable,new [] {Convert.ChangeType (setter.Value, pinfo.PropertyType.GetTypeInfo())});
}
});
public static Style GetStyle (BindableObject bindable)
{
return (Style)bindable.GetValue (StyleProperty);
}
public static void SetStyle (BindableObject bindable, Style value)
{
bindable.SetValue (StyleProperty, value);
}
}
}
Obviously, the code doing the assignation is very light and you might have to adapt it to your needs (support enums, etc.), but it works in this simplistic case.
I'm sure it'll help.
Style support is now available:
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Label">
<Setter Property="HorizontalOptions" Value="LayoutOptions.Center" />
<Setter Property="VerticalOptions" Value="LayoutOptions.Center" />
<Setter Property="FontSize" Value="48" />
<Setter Property="FontAttributes" Value="Bold, Italic" />
<Setter Property="Opacity" Value=".5" />
<Setter Property="TextColor" Value="Black" />
<Setter Property="Text" Value="Copied" />
<Setter Property="IsVisible" Value="False" />
</Style>
</ResourceDictionary>
</ContentPage.Resources>
From Xamarin.Forms 1.3 you get more styling options.
Xamarin.Forms 1.3 Technology Preview
Support styles in XAML and in code
Allow styles to be based on DynamicResources via Style.BaseResourceKey
Add platform specific styles via Device.Styles (supports iOS dynamic type)
I was able to create code that is closer to a ResourceDictionary that I was more accustomed to in WPF/Silverlight and remove it from the actual page (view)
Instead of having just App.cs, I created a new xaml file.. App.xaml along with it's accompanying code-behind of App.xaml.cs. I took what was in the App.cs and put it into the App.xaml.cs.
public partial class App : Application
{
public App()
{
var _mainView = new MainView();
var _navPage = new NavigationPage(_mainView);
MainPage = _navPage;
}
//.. the methods below are currently empty on mine,
//still figuring out some of the front end ..
protected override void OnStart(){ }
protected override void OnSleep(){ }
protected override void OnResume(){ }
}
This allowed me in the App.xaml to declare application resources I can use on other views:
<Style x:Key="buttonStyle" TargetType="{x:Type Button}">
<Setter Property="TextColor" Value="Blue"/>
<!-- note: I tried Foreground and got errors, but once I
used TextColor as the property, it ran fine and the
text of the button I had changed color to what I expected -->
<Setter Property="BackgroundColor" Value="White"/>
<!-- note: I tried Background and got errors, but once I
used BackgroundColor as the property, it ran fine and the
background of the button I had changed color -->
</Style>
</ResourceDictionary>
And then I was able to use the style on my pages by using StaticResource
<Button Text="Inquiry" Style="{StaticResource buttonStyle}"/>
There are probably dozens of ways, maybe even better - but this helped me keep all of my styles in one location (App.xaml)
P.S. I don't know why it isn't formatting the codebehind example, I have tried a few times but hopefully you still understand it.
I'm contemplating this issue with Xamarin Forms myself, being a Silverlight developer.
An optional approach would be to create your own controls ("Views" in Xamarin terms), either in XAML or code, and use these when building your interface.
I know I have seen this problem before somewhere, but I'm not sure if there was an answer at the time. I'm trying to add SpellCheck to a TextBox in WPF, .NET 4.0. It works fine in terms of finding and marking the incorrect words, and will replace the first word in the TextBox if it's incorrect. Anything past word one though, and it just moves the carat to the start of the TextBox without changing anything? As I said I saw this somewhere about 6-9 months ago, but now everything I come up with in google deals with alternate languages (I'm staying strictly in English for now). I've included the event methods and styling XAML only for completeness, I don't think the issue lies there.
XAML:
<MultiBox:MultiBox Name="callNotes" Grid.Column="1" Width="Auto" Height="Auto" Margin="2,5,15,20" VerticalAlignment="Stretch" AcceptsReturn="True" FontWeight="Bold" GotFocus="callNotes_GotFocus" SelectAllOnGotFocus="False" SpellCheck.IsEnabled="True" xml:lang="en-US" Style="{StaticResource TextBoxStyle}" TextChanged="callNotes_TextChanged" TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" />
<Style x:Key="TextBoxStyle" TargetType="{x:Type MyNamespace:MultiBox}">
<Setter Property="CharacterCasing" Value="Upper" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Height" Value="23" />
<Setter Property="Width" Value="Auto" />
<Setter Property="SelectAllOnGotFocus" Value="True" />
<Setter Property="TextWrapping" Value="Wrap" />
</Style>
Code:
private void callNotes_TextChanged(object sender, TextChangedEventArgs e)
{
callNotes.Text.ToUpper();
lineCountOne.Content = ((callNotes.Text.Length / 78) + 1);
}
private void callNotes_GotFocus(object sender, RoutedEventArgs e)
{
callNotes.CaretIndex = callNotes.Text.Length;
}
It would help to see your code which attempts to correct the errors. Here's simple code which loops through all the detected errors and accepts the first suggestion. If you only want to fix particular errors, you'll need to skip to the particular error you're interested in by getting the error at a certain index.
int ndx;
while ((ndx = callNotes.GetNextSpellingErrorCharacterIndex(0, LogicalDirection.Forward)) != -1)
{
var err = callNotes.GetSpellingError(ndx);
foreach (String sugg in err.Suggestions)
{
err.Correct(sugg);
break;
}
}
After trying jschroedl's suggestion and still having no luck (although I do know that his answer should have been correct), I started playing with every possible setting I could think of, even to the point of creating a completely new WPF project with a single, Spellcheck-enabled TextBox just to make sure it wasn't something with the Visual Studio/.NET installation itself. Turns out it wasn't, it was something I had done months ago to ensure that selecting any given TextBox through the program would result in the SelectAll() method being triggered. Once I screened out this particular TextBox from that bit of code, all works great. Again, thanks to jschroedl, I know there is no way he could have known this. The offending code is below, in case anybody comes across a similar issue.
protected override void OnStartup(StartupEventArgs e)
{
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotKeyboardFocusEvent, new RoutedEventHandler(SelectAllText), true);
base.OnStartup(e);
}
protected static void SelectAllText(object sender, RoutedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null && textBox.Name != "callNotes")
textBox.SelectAll();
}
Adding the && textBox.Name != "callNotes" solved the problem.