简体   繁体   中英

How do I dynamicaly update a child control within a form every second without changing the code behind the form?

I have a child control within a form which shows the strength of the WiFi signal As this application is to be used on a tablet whithin a moving vehicle round the site. I would like to have this control within every form of my WPF application and I would like it to refresh every second without having to change the code behind each form it is on.

It is possible to refresh this control using the parent form's code behind by calling signalQualityView.Refresh(); every second but would like this functionality to be implemented within the SignalQualityView usercontrol .


public partial class SignalQualityView : UserControl, INotifyPropertyChanged
    {
        // wifi signal indicator taken from from https://stackoverflow.com/questions/20085284/c-sharp-wpf-rating-control-similar-to-wifi-signal-indicator

        private NetworkInformationService _networkInformationService;

        public SignalQualityView()
        {
            InitializeComponent();
            DataContext = this;
            _networkInformationService = new NetworkInformationService();
            Refresh();

        }

        public void Refresh()
        {
            Task.Run(() =>
            {
                WirelessNetwork wirelessNetwork = _networkInformationService.GetWirelessNetworkDetails();
                var signalQuality = wirelessNetwork.SignalQuality;

                if (signalQuality >= 80)
                    RatingValue = 5;
                else if (signalQuality >= 60)
                    RatingValue = 4;
                else if (signalQuality >= 40)
                    RatingValue = 3;
                else if (signalQuality >= 20)
                    RatingValue = 2;
                else if (signalQuality >= 1)
                    RatingValue = 1;
                else
                    RatingValue = 0;
                Task.Delay(1000);
                Refresh();
            });
        }



        public int RatingValue
        {
            get { return (int)GetValue(RatingValueProperty); }
            set
            {
                SetValue(RatingValueProperty, value);
                OnPropertyChanged();
            }
        }

        // Using a DependencyProperty as the backing store for RatingValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty RatingValueProperty =
            DependencyProperty.Register("RatingValue", typeof(int), typeof(SignalQualityView), new UIPropertyMetadata(0));

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class RatingConverter : IValueConverter
    {
        public Brush OnBrush { get; set; }
        public Brush OffBrush { get; set; }

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int rating = 0;
            int number = 0;
            if (int.TryParse(value.ToString(), out rating) && int.TryParse(parameter.ToString(), out number))
            {
                if (rating >= number)
                {
                    return OnBrush;
                }
                return OffBrush;
            }
            return Brushes.Transparent;
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

<UserControl x:Class="App.PlugIn.Controls.SignalQualityView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:DPWorld.PlugIn.Controls"
             mc:Ignorable="d" 
            Height="40" Width="60">


    <Grid Background="black"  >
        <Grid.Resources>
            <local:RatingConverter x:Key="RatingConverter" OnBrush="LightBlue" OffBrush="Black" />
            <Style TargetType="Rectangle">
                <Setter Property="HorizontalAlignment" Value="Left" />
                <Setter Property="VerticalAlignment" Value="Bottom" />
                <Setter Property="Margin" Value="5,0,0,0" />
            </Style>
        </Grid.Resources>

        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Background="Black" VerticalAlignment="Center">
            <Rectangle Width="5" Height="5" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=1}"/>
            <Rectangle Width="5" Height="10" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=2}"/>
            <Rectangle Width="5" Height="15" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=3}"/>
            <Rectangle Width="5" Height="20" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=4}"/>
            <Rectangle Width="5" Height="25" Fill="{Binding RatingValue, Converter={StaticResource RatingConverter}, ConverterParameter=5}"/>
        </StackPanel>
        <Label Content="SIGNAL" Foreground="LightBlue" VerticalAlignment="Top" Height="15" FontSize="8" Margin="10,0,18,0" Padding="0,0,0,0"/>
    </Grid>
</UserControl>
<Window x:Class="WpfApplication1.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:pi="clr-namespace:App.PlugIn.Controls;assembly=PlugIn"
    Title="Window1" Height="300" Width="300">
    <Grid>
   <pi:SignalQualityView Name="signalQualityView" />
    </Grid>
</Window>

The number of signal bars are meant highlighted depending on the signal strength but 0 bars are highlighted.

When using WPF, data on a window that changes should come from an IObservable property of the bound object. When the property changes, the display value changes. Put the current value into a property of a shared object using a task that runs every second. Share the object across all bound objects and no code behind is required.

You start of with a class that can be shared and observed:

   public class WiFiStrength : IObservable<int>
   {
      private int _signalQuality;
      private int _ratingValue;

      private class Unsubscriber : IDisposable
      {
         private List<IObserver<int>> _observers;
         private readonly IObserver<int> _observer;

         public Unsubscriber(List<IObserver<int>> observers, IObserver<int> observer)
         {
            _observers = observers;
            _observer = observer;
         }

         public void Dispose()
         {
            if (_observers != null)
            {
               _observers.Remove(_observer);
               _observers = null;
            }
         }
      }

      private List<IObserver<int>> _observers = new List<IObserver<int>>();

      private void SetAndRaiseIfChanged(int newRating)
      {
         if (_ratingValue != newRating)
         {
            _ratingValue = newRating;
            foreach (var observer in _observers)
            {
               observer.OnNext(newRating);
            }
         }
      }

      private static WiFiStrength _sharedInstance;
      private static Task _updater;
      private static CancellationTokenSource cts;

      private static void UpdateStrength(WiFiStrength model, CancellationToken ct)
      {
         var rnd = new Random();
         while (!ct.IsCancellationRequested)
         {
            model.SignalQuality = rnd.Next(100);
            Thread.Sleep(1000);
         }
      }

      public static WiFiStrength SharedInstance()
      {
         if (_sharedInstance == null)
         {
            _sharedInstance = new WiFiStrength();
            cts = new CancellationTokenSource();
            _updater = new Task(() => UpdateStrength(_sharedInstance, cts.Token));
            _updater.Start();
         }

         return _sharedInstance;
      }

      public int SignalQuality
      {
         get => _signalQuality;
         set
         {
            _signalQuality = value;
            if (_signalQuality >= 80)
            {
               SetAndRaiseIfChanged(5);
            }
            else if (_signalQuality >= 60)
            {
               SetAndRaiseIfChanged(4);
            }
            else if (_signalQuality >= 40)
            {
               SetAndRaiseIfChanged(3);
            }
            else if (_signalQuality >= 20)
            {
               SetAndRaiseIfChanged(2);
            }
            else if (_signalQuality >= 1)
            {
               SetAndRaiseIfChanged(1);
            }
            else
            {
               SetAndRaiseIfChanged(0);
            }
         }
      }

      public int SignalRating => _ratingValue;

      public IDisposable Subscribe(IObserver<int> observer)
      {
         _observers.Add(observer);
         return new Unsubscriber(_observers, observer);
      }
   }

You attach it to your bound class:

   public class ViewModel1 : INotifyPropertyChanged
   {
      public string TextField { get; set; }
      public WiFiStrength WiFi { get; set; }

      private IObserver<int> _observer;

      private class WiFiObserver : IObserver<int>
      {
         private readonly ViewModel1 _parent;
         private readonly IDisposable _unsubscribe;

         public WiFiObserver(ViewModel1 parent, WiFiStrength observed)
         {
            _parent = parent;
            _unsubscribe = parent.WiFi.Subscribe(this);
         }

         public void OnNext(int value)
         {
            _parent.NotifyPropertyChanged("WiFi");
         }

         public void OnError(Exception error)
         {
            _unsubscribe.Dispose();
         }

         public void OnCompleted()
         {
            _unsubscribe.Dispose();
         }
      }

      public ViewModel1()
      {
         WiFi = WiFiStrength.SharedInstance();
         _observer = new WiFiObserver(this, WiFi);
      }

      public event PropertyChangedEventHandler PropertyChanged;

      private void NotifyPropertyChanged(string propertyName)
      {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
   }

Your class looks for changes, and notifies the owning form if it does change.

Your XAML need only bind to the property like this:

         <Label Content="{Binding WiFi.SignalRating, Mode=OneWay}"/>

Use on as many forms as you like.

A simple UserControl that updates itself periodically would use a DispatcherTimer with a Tick event handler that updates a dependency property of the control. An element in the control's XAML would bind the control property by a RelativeSource Binding.

A simple digital clock as example:

<UserControl ...>
    <Grid>
        <TextBlock Text="{Binding Time,
            RelativeSource={RelativeSource AncestorType=UserControl}}"/>
    </Grid>
</UserControl>

Code behind:

public partial class Clock : UserControl
{
    public Clock()
    {
        InitializeComponent();

        var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(0.5) };
        timer.Tick += (s, e) => Time = DateTime.Now.ToString("HH:mm:ss");
        timer.Start();
    }

    public static readonly DependencyProperty TimeProperty =
        DependencyProperty.Register(nameof(Time), typeof(string), typeof(Clock));

    public string Time
    {
        get { return (string)GetValue(TimeProperty); }
        set { SetValue(TimeProperty, value); }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM