Related
I have a couple of asks to update some text throughout a project. Doing a find and replace in code is easy, but I would like to implement a tool to look through all game objects. Unfortunately, I can grab all of the game objects in the scene, but I have not found a solution to grab all of the game objects in the project. Does anyone have any suggestions?
This is my current approach:
using System;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using EditorPlayerSettings = UnityEditor.PlayerSettings;
namespace UnityEditor.XCodeEditor
{
public class FindAndReplaceToolbar : EditorWindow
{
private const string TAG = "FindAndReplace";
string mOldValue = string.Empty;
string mNewValue = string.Empty;
[MenuItem("Window/Find And Replace")]
void Init()
{
var window = GetWindow<FindAndReplaceToolbar>(false, "Find And Replace Toolbar");
window.minSize = new Vector2(10, 10);
window.Show();
}
void OnGUI()
{
GUILayout.BeginHorizontal();
{
mOldValue = EditorGUILayout.TextField("Find: ", mOldValue, GUILayout.ExpandWidth(true));
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
{
mNewValue = EditorGUILayout.TextField("Replace: ", mNewValue, GUILayout.ExpandWidth(true));
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Find and Replace"))
{
FindAndReplace();
}
GUILayout.EndHorizontal();
}
#region Find And Replace Helper
public void FindAndReplace()
{
Debug.Log($"{TAG} Started: ({mOldValue} - {mNewValue})");
foreach (var textMeshProUGUI in GetAllTextMeshProUGUIsInScene())
{
if (textMeshProUGUI.text.Contains(mOldValue))
{
Debug.Log($"{TAG} Replaced {mOldValue} in {textMeshProUGUI.name} with {mNewValue} (See {GetFileName(textMeshProUGUI.transform)})");
textMeshProUGUI.text = textMeshProUGUI.text.Replace(mOldValue, mNewValue);
}
}
Debug.Log($"{TAG} Finished: ({mOldValue} - {mNewValue})");
}
public List<TextMeshProUGUI> GetAllTextMeshProUGUIsInScene()
{
List<TextMeshProUGUI> objectsInScene = new List<TextMeshProUGUI>();
foreach (TextMeshProUGUI go in (TextMeshProUGUI[]) Resources.FindObjectsOfTypeAll(typeof(TextMeshProUGUI)))
{
if (!EditorUtility.IsPersistent(go.transform.root.gameObject) && !(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave))
{
objectsInScene.Add(go);
}
}
return objectsInScene;
}
public static string GetFileName(Transform transform)
{
try
{
var parent = transform;
while (parent.parent != null)
{
parent = parent.parent;
}
return parent.name;
}
catch (Exception)
{
return transform.name;
}
}
#endregion Find And Replace Helper
}
}
#hijinxbassist pointed me in the right direction. Here is my current solution:
using System;
using System.IO;
using UnityEngine;
using EditorPlayerSettings = UnityEditor.PlayerSettings;
namespace UnityEditor.XCodeEditor
{
[InitializeOnLoad]
public class FindAndReplaceToolbar : EditorWindow
{
private const string TAG = "FindAndReplace";
private string mOldValue = string.Empty;
private string mNewValue = string.Empty;
static FindAndReplaceToolbar()
{
Debug.Log($"{TAG} InitializeOnLoad");
}
[MenuItem("Window/Find And Replace")]
static void Init()
{
var window = GetWindow<FindAndReplaceToolbar>(false, "Find And Replace Toolbar");
window.minSize = new Vector2(10, 10);
window.Show();
}
void OnGUI()
{
GUILayout.BeginHorizontal();
{
mOldValue = EditorGUILayout.TextField("Find: ", mOldValue, GUILayout.ExpandWidth(true));
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
{
mNewValue = EditorGUILayout.TextField("Replace: ", mNewValue, GUILayout.ExpandWidth(true));
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Find and Replace"))
{
FindAndReplace();
}
GUILayout.EndHorizontal();
}
public void FindAndReplace()
{
Debug.Log($"{TAG}: Started: ({mOldValue} - {mNewValue})");
string[] assetGUIDs = AssetDatabase.FindAssets("t:Object");
for (int i = 0; i < assetGUIDs.Length; i++)
{
try
{
string guid = assetGUIDs[i];
string assetFilePath = AssetDatabase.GUIDToAssetPath(guid);
string assetFile = File.ReadAllText(assetFilePath);
if (assetFile.Contains(mOldValue))
{
assetFile = assetFile.Replace(mOldValue, mNewValue);
File.WriteAllText(assetFilePath, assetFile);
Debug.Log($"{TAG}: Replaced {mOldValue} with {mNewValue} in Asset: [{Path.GetFileName(assetFilePath)}] (Type: {AssetDatabase.GetMainAssetTypeAtPath(assetFilePath)})");
}
}
catch (Exception e)
{
Debug.Log($"{TAG}: {e.Message}");
}
}
Debug.Log($"{TAG}: Finished: ({mOldValue} - {mNewValue})");
}
}
}
Guys
I've implemented into my Unity project a plugin which can be downloaded at the link (https://github.com/HoseinPorazar/Android-Native-TTS-plugin-for-Unity-3d).
To use it you need:
1-import AndroidNativeTTS.unitypackage into your project
2-create an empty game object and rename it to tts.
3-attach test script and TextToSpeech script to tts game object.
4-add a button and set the on click event to test.Speak().
5-build project for Android platform.
After implementing the plugin to make it work it's necessary to delete in AndroidManifext.xml the line "android:label=NAtive TTS".
AndroidManifest.xml is stored in the directory "Assets\Plugin\Android\androidtts-release.aar"
When I implemented the plugin which is build to use "UK" and "US" locale, I tried to add Russian locale but unfortunately it failed.
Please see the scripts below (Test.cs and TextToSpeach.cs):
test.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour {
TextToSpeech tts;
void Start()
{
tts = GetComponent<TextToSpeech>();
}
public void Speak()
{
tts.Speak("hello mr hosein porazar kasin", (string msg) =>
{
tts.ShowToast(msg);
});
}
public void ChangeSpeed()
{
tts.SetSpeed(0.5f);
}
public void ChangeLanguage()
{
tts.SetLanguage(TextToSpeech.Locale.UK);
}
public void ChangePitch()
{
tts.SetPitch(0.6f);
}
}
TextToSpeech.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TextToSpeech :MonoBehaviour
{
void Start()
{
}
public enum Locale
{
UK = 0,
US = 1
}
private AndroidJavaObject TTSExample = null;
private AndroidJavaObject activityContext = null;
private Locale _lang;
public Locale Language { get { return _lang; } set { SetLanguage(value); } }
private float _pitch, _speed;
public float Pitch { get{return _pitch;} set { SetPitch(value); } }
public float Speed { get{return _speed;} set { SetSpeed(value); } }
public delegate void OnErrorCallbackHandler(string error);
private OnErrorCallbackHandler _callback;
public TextToSpeech()
{
//Initialize();
}
public TextToSpeech(Locale language)
{
Initialize();
this.Language = language;
SetLanguage(this.Language);
}
public TextToSpeech(Locale language,float speed,float pitch)
{
Initialize();
this.Language = language;
this.Pitch = pitch;
this.Speed = speed;
SetLanguage(this.Language);
SetSpeed(this.Speed);
SetPitch(this.Pitch);
}
public void Speak(string toSay,OnErrorCallbackHandler callback)
{
if (TTSExample == null)
{
Initialize();
}
this._callback = callback;
TTSExample.Call("TTSMEWithCallBack", toSay, gameObject.name, "OnError");
}
public void OnError(string error)
{
if (_callback != null)
{
if (error.Length > 0)
{
_callback.Invoke(error);
}
}
ShowToast(error);
}
public void Speak(string toSay)
{
if (TTSExample == null)
{
Initialize();
}
TTSExample.Call("TTSME", toSay);
}
public void SetLanguage(Locale lan)
{
this._lang = lan;
string[] Language = new string[] {"UK","US" };
if (TTSExample == null)
{
Initialize();
}
TTSExample.Call("SetLang", Language[(int)lan]);
}
public void SetSpeed(float speed)
{
this._speed = speed;
if (TTSExample == null)
{
Initialize();
}
TTSExample.Set<float>("Speed", speed);
}
public void SetPitch(float pitch)
{
this._pitch = pitch;
if (TTSExample == null)
{
Initialize();
}
TTSExample.Set<float>("Pitch", pitch);
}
private void Initialize()
{
if (TTSExample == null)
{
using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
}
using (AndroidJavaClass pluginClass = new AndroidJavaClass("ir.hoseinporazar.androidtts.TTS"))
{
if (pluginClass != null)
{
TTSExample = pluginClass.CallStatic<AndroidJavaObject>("instance");
TTSExample.Call("setContext", activityContext);
}
}
}
}
public void ShowToast(string msg)
{
if (TTSExample == null)
{
using (AndroidJavaClass activityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
activityContext = activityClass.GetStatic<AndroidJavaObject>("currentActivity");
}
using (AndroidJavaClass pluginClass = new AndroidJavaClass("ir.hoseinporazar.androidtts.TTS"))
{
if (pluginClass != null)
{
TTSExample = pluginClass.CallStatic<AndroidJavaObject>("instance");
TTSExample.Call("setContext", activityContext);
activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
TTSExample.Call("showMessage", msg);
}));
}
}
}
else
{
activityContext.Call("runOnUiThread", new AndroidJavaRunnable(() =>
{
TTSExample.Call("showMessage", msg);
}));
}
}
}
I've tried to format following lines to change the language to Russian but it didn't help me:
public enum Locale
{
RU = 0,
US = 1
}
public void SetLanguage(Locale lan)
{
this._lang = lan;
string[] Language = new string[] {"RU","US" };
if (TTSExample == null)
{
Initialize();
}
TTSExample.Call("SetLang", Language[(int)lan]);
}
I also tried to contatct the developer of the plugin but it seems he was last time on github a couple years ago.
I'd really appreciate it if somebody can help me with the issue.
On the plugin homepage, the author says...
I have included 2 languages (UK,and US) if you want to use other
languages you will have to modify plugin ( with Android Studio).
So it seems you will also need to change switch statement at the bottom of the Java code here:
https://github.com/HoseinPorazar/Android-Native-TTS-plugin-for-Unity-3d/blob/master/NativeAndroidTTS/androidtts/src/main/java/ir/hoseinporazar/androidtts/TTS.java
By adding an "RU" case like this...
public void SetLang(String loc){
switch (loc){
case "UK":
if(t1!=null)
t1.setLanguage(Locale.UK);
break;
case "US":
if(t1!=null)
t1.setLanguage(Locale.US);
break;
case "RU":
if(t1!=null)
t1.setLanguage(Locale.RU);
break;
}
}
So I am designing an application for windows laptops to connect to a custom designed pressure sensor. The application pairs to the device and then receives notifications from the device every 10 ms. Then for some reason the communication stops. I know it is a problem with my application and not with the device, because when I connect to my phone, I do not have this problem.
Here is the main page where I create the devicewatcher and discover the device:
using System;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
private DeviceWatcher deviceWatcher;
private ObservableCollection<DeviceInformation> deviceList = new ObservableCollection<DeviceInformation>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.DataContext = deviceList;
deviceListView.ItemsSource = deviceList;
deviceWatcher = DeviceInformation.CreateWatcher(
"System.ItemNameDisplay:~~\"Button\"",
new string[] {
"System.Devices.Aep.DeviceAddress",
"System.Devices.Aep.IsConnected" },
DeviceInformationKind.AssociationEndpoint);
deviceWatcher.Added += DeviceWatcher_Added;
deviceWatcher.Removed += DeviceWatcher_Removed;
deviceWatcher.Start();
base.OnNavigatedTo(e);
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Collapsed;
}
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
deviceWatcher.Stop();
base.OnNavigatedFrom(e);
}
private async void DeviceWatcher_Removed(DeviceWatcher sender, DeviceInformationUpdate args)
{
var toRemove = (from a in deviceList where a.Id == args.Id select a).FirstOrDefault();
if (toRemove != null)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Remove(toRemove); });
}
}
private async void DeviceWatcher_Added(DeviceWatcher sender, DeviceInformation args)
{
await this.Dispatcher.RunAsync(
Windows.UI.Core.CoreDispatcherPriority.Normal,
() => { deviceList.Add(args); });
}
private void deviceListView_ItemClick(object sender, ItemClickEventArgs e)
{
this.Frame.Navigate(typeof(DevicePage), e.ClickedItem);
}
}
}'
This next code is the page where the pressure sensor is connected to and where data is read from the device.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using Windows.UI.Popups;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Devices.Bluetooth;
using Windows.Devices.Enumeration;
using Windows.Storage.Pickers;
using Windows.Storage;
using Windows.Storage.Streams;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238
namespace BLEInterfaceTest
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class DevicePage : Page
{
private DeviceInformation device { get; set; }
private PressureSensor pSensor { get; set; }
public static DateTime startTime { get; set; }
public ObservableCollection<DataPoint> PressureData = new ObservableCollection<DataPoint>();
public static ObservableCollection<DataPoint> inbetween;
private static TextBox txtP;
private BluetoothLEDevice leDevice;
private DispatcherTimer timer = new DispatcherTimer();
private int packetNum = 0;
public DevicePage()
{
this.InitializeComponent();
SystemNavigationManager.GetForCurrentView().BackRequested += DevicePage_BackRequested;
txtP = txtValue1;
inbetween = PressureData;
}
public static void ChangeText(string text)
{
txtP.Text = text;
}
private async void InitializePressureSensor(GattDeviceService service)
{
pSensor = new PressureSensor(service, SensorUUIDs.PressureSensorUuid);
await pSensor.EnableNotifications();
btnStart.IsEnabled = true;
}
private async void StartRecievingData()
{
try
{
leDevice = await BluetoothLEDevice.FromIdAsync(device.Id);
string selector = "(System.DeviceInterface.Bluetooth.DeviceAddress:=\"" +
leDevice.BluetoothAddress.ToString("X") + "\")";
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
foreach (var service in services.Services)
{
if (service.Uuid.ToString() == SensorUUIDs.ButtonSensorServiceUuid)
{
InitializePressureSensor(service);
}
}
timer.Interval = new TimeSpan(0, 0, 0, 0, 1);
timer.Tick += Timer_Tick1;
startTime = DateTime.Now;
timer.Start();
}
catch (Exception ex)
{
var messageDialog = new MessageDialog("An error has occured Please try again. \n" + ex.Message, "Error!");
}
}
public async void UpdateAllData()
{
while (pSensor != null && pSensor.MorePacketsAvailable)
{
int[] values = await pSensor.GetPressure();
int packetNumber = values[0];
if (packetNumber > packetNum)
{
packetNum = packetNumber;
txtValue1.Text = Convert.ToString(values[1]);
txtValue2.Text = Convert.ToString(values[5]);
for (int i = 1; i < 5; i++)
{
PressureData.Add(new DataPoint(DateTime.Now - startTime, packetNumber, ((i-1)*2.5 + 10*packetNumber), values[i], values[i + 4]));
}
}
}
}
private void Timer_Tick1(object sender, object e)
{
UpdateAllData();
}
private async void PairToDevice()
{
if (device.Pairing.CanPair)
{
var customPairing = device.Pairing.Custom;
customPairing.PairingRequested += CustomPairing_PairingRequested;
var result = await customPairing.PairAsync(DevicePairingKinds.ConfirmOnly);
customPairing.PairingRequested -= CustomPairing_PairingRequested;
if ((result.Status == DevicePairingResultStatus.Paired) || (result.Status == DevicePairingResultStatus.AlreadyPaired))
{
/*while (device.Pairing.IsPaired == false)
{
device = await DeviceInformation.CreateFromIdAsync(device.Id);
}*/
StartRecievingData();
}
}
else if (device.Pairing.IsPaired)
{
StartRecievingData();
}
}
private void CustomPairing_PairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
{
args.Accept();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
btnSave.Content = "Save";
btnStop.IsEnabled = false;
btnStart.IsEnabled = false;
this.DataContext = PressureData;
device = (DeviceInformation)e.Parameter;
PairToDevice();
//StartRecievingData();
base.OnNavigatedTo(e);
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame.CanGoBack)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility =
AppViewBackButtonVisibility.Visible;
}
}
private void DevicePage_BackRequested(object sender, BackRequestedEventArgs eventArgs)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{
return;
}
// Navigate back if possible, and if the event has already been handled
if (rootFrame.CanGoBack && eventArgs.Handled ==false)
{
eventArgs.Handled = true;
rootFrame.GoBack();
}
}
private async void btnSave_Click(object sender, RoutedEventArgs e)
{
timer.Stop();
var picker = new FileSavePicker();
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeChoices.Add("CSV", new List<string>() { ".csv" });
StorageFile file = await picker.PickSaveFileAsync();
if (file != null)
{
var stream = await file.OpenAsync(FileAccessMode.ReadWrite);
using (IOutputStream outputStream = stream.GetOutputStreamAt(0))
{
using (var writer = new DataWriter(outputStream))
{
foreach (DataPoint p in PressureData)
{
string text = p.TimeStamp.ToString() + "," + p.PacketNumber.ToString() + "," + p.InternalTimestamp.ToString() + "," + p.PressureValue1.ToString() + "," + p.PressureValue2.ToString() + "\n";
writer.WriteString(text);
}
await writer.StoreAsync();
await writer.FlushAsync();
}
}
stream.Dispose();
}
}
private async void btnStart_Click(object sender, RoutedEventArgs e)
{
if (pSensor != null)
{
btnStop.IsEnabled = true;
btnStart.IsEnabled = false;
startTime = DateTime.Now;
if (pSensor != null)
{
await pSensor.BeginCollecting();
}
}
}
private async void btnStop_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = true;
btnStop.IsEnabled = false;
if (pSensor != null)
{
await pSensor.StopCollecting();
}
}
}
}
Here is where I define my SensorBase and PressureSensor class that handles the device connection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.GenericAttributeProfile;
using Windows.Storage.Streams;
using Windows.Devices.Enumeration;
namespace BLEInterfaceTest
{
public static class SensorUUIDs
{
private static readonly string _packetUuid = "0000a043-0000-1000-8000-00805f9b34fb";
private static readonly string _buttonSensorServiceUuid = "0000a042-0000-1000-8000-00805f9b34fb";
private static readonly string _sensorStateUuid = "0000a044-0000-1000-8000-00805f9b34fb";
public static string PressureSensorUuid
{
get { return _packetUuid; }
}
public static string ButtonSensorServiceUuid
{
get { return _buttonSensorServiceUuid; }
}
public static string SensorStateUuid
{
get { return _sensorStateUuid; }
}
}
public class SensorBase : IDisposable
{
protected GattDeviceService deviceService;
protected string sensorDataUuid;
protected Queue<byte[]> fifoBuffer;
protected bool isNotificationSupported = false;
public bool newData = false;
private GattCharacteristic dataCharacteristic;
public SensorBase(GattDeviceService dataService, string sensorDataUuid)
{
this.deviceService = dataService;
this.sensorDataUuid = sensorDataUuid;
fifoBuffer = new Queue<byte[]>(20);
}
public bool MorePacketsAvailable
{
get
{
if (fifoBuffer.Count > 0)
{
return true;
}
else
{
return false;
}
}
}
public virtual async Task EnableNotifications()
{
GattCharacteristicsResult result = await deviceService.GetCharacteristicsAsync();
foreach (var test in result.Characteristics)
{
string t = test.Uuid.ToString();
}
isNotificationSupported = true;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged += dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
var currentDescriptorValue = await dataCharacteristic.ReadClientCharacteristicConfigurationDescriptorAsync();
if (currentDescriptorValue.Status != GattCommunicationStatus.Success
|| currentDescriptorValue.ClientCharacteristicConfigurationDescriptor != GattClientCharacteristicConfigurationDescriptorValue.Notify)
{
GattCommunicationStatus status2 = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
}
}
public virtual async Task DisableNotifications()
{
newData = false;
isNotificationSupported = false;
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
dataCharacteristic.ValueChanged -= dataCharacteristic_ValueChanged;
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(GattClientCharacteristicConfigurationDescriptorValue.None);
}
protected async Task<byte[]> ReadValue()
{
if (!isNotificationSupported)
{
if (dataCharacteristic == null)
{
dataCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(sensorDataUuid))).Characteristics[0];
}
GattReadResult readResult = await dataCharacteristic.ReadValueAsync();
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
fifoBuffer.Enqueue(data);
}
return fifoBuffer.Dequeue();
}
protected async Task WriteByteArray(string characteristicUuid, byte[] value)
{
GattCharacteristic writeCharacteristic = (await deviceService.GetCharacteristicsForUuidAsync(
new Guid(characteristicUuid))).Characteristics[0];
var writer = new DataWriter();
writer.WriteBytes(value);
var res = await writeCharacteristic.WriteValueAsync(writer.DetachBuffer(), GattWriteOption.WriteWithoutResponse);
}
private void dataCharacteristic_ValueChanged(GattCharacteristic sender, GattValueChangedEventArgs args)
{
byte[] data = new byte[args.CharacteristicValue.Length];
DataReader.FromBuffer(args.CharacteristicValue).ReadBytes(data);
fifoBuffer.Enqueue(data);
newData = true;
}
public async void Dispose()
{
await DisableNotifications();
}
}
public class PressureSensor: SensorBase
{
public PressureSensor(GattDeviceService dataService, string sensorDataUuid)
: base(dataService, sensorDataUuid)
{
}
public async Task BeginCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
public async Task<int[]> GetPressure()
{
byte[] data = await ReadValue();
if (data != null)
{
int[] values = new int[9];
values[0] = (int)BitConverter.ToInt32(data, 0);
for (int i = 1; i < values.Length; i++)
{
values[i] = (int)BitConverter.ToInt16(data, 2 * i + 2);
}
return values;
}
else
{
return new int[] { 0 };
}
}
public async Task StopCollecting()
{
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x00 });
}
}
}
Here is the DataPoint Class that I use to organize the data received from the pressure sensor:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace BLEInterfaceTest
{
public class DataPoint : INotifyPropertyChanged
{
private TimeSpan _timestamp;
private int _packetNumber;
private double _internalTimestamp;
private int _pressure1;
private int _pressure2;
public event PropertyChangedEventHandler PropertyChanged;
public TimeSpan TimeStamp
{
get { return _timestamp; }
set
{
_timestamp = value;
this.NotifyPropertyChanged();
}
}
public int PacketNumber
{
get { return _packetNumber; }
set
{
_packetNumber = value;
this.NotifyPropertyChanged();
}
}
public double InternalTimestamp
{
get { return _internalTimestamp; }
set
{
_internalTimestamp = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue1
{
get { return _pressure1; }
set
{
_pressure1 = value;
this.NotifyPropertyChanged();
}
}
public int PressureValue2
{
get { return _pressure2; }
set
{
_pressure2 = value;
this.NotifyPropertyChanged();
}
}
public DataPoint(TimeSpan time,int packetNumber, double internalTimestamp, int pressure1, int pressure2)
{
_timestamp = time;
_packetNumber = packetNumber;
_internalTimestamp = internalTimestamp;
_pressure1 = pressure1;
_pressure2 = pressure2;
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
if (!string.IsNullOrEmpty(propertyName))
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I have researched this extensively, and all I could find was help on how to initiate a disconnection. I have the opposite problem. One page I found stated that the problem might be caused by the device not properly storing the bonding state, but I have checked that and I did initialize the device to save the bonding state.
Interestingly if I do not pair the device to the computer before trying to read information from it then I do not have the problem. The connection never randomly stops. But when I do this, the computer does not receive every packet of data sent from the sensor device. It will receive one or two packets and then skip five or six packets. If I pair the device then I will receive every packet but the connection will randomly cut off.
So my question is two fold, I guess. How do I stop the connection from cutting off when the device is paired? Or alternatively, is there a way to allow the application to receive every packet of data when it is not paired?
UPDATE
I realized I should include more information on my sensor peripheral in case the error is in that code. I am currently designing a rapid prototyping of this sensor before I move on to designing the embedded version. To do this, I am using the BLE Nano 1 from RedBearLabs as a user friendly prototype. I am programing this device with the online MBED compiler. I have included the nRF51822 and BLE_API libraries to handle the bluetooth low energy communication.
UPDATE 2
So after more research into what is causing the problem, I have found that the disconnection occurs when a connection interval and a generation 2 garbage collection occur at the same time. In UWP the garbage collector can pause the UI Thread for generation 2 collections. (see here)
My thought is that if the thread is paused at the beginning of a connection interval, then the central is not able to initiate the connection with the peripheral and the peripheral therefore thinks the client is no longer listening (see more about how BLE connections work).
I discovered this by finding out exactly what is necessary to get the connection back once it has randomly stopped. I started with the entire connection process and reduced it down to this:
public async Task ReconnectDevice()
{
GattCommunicationStatus status = await dataCharacteristic.WriteClientCharacteristicConfigurationDescriptorAsync(
GattClientCharacteristicConfigurationDescriptorValue.Notify);
await WriteByteArray(SensorUUIDs.SensorStateUuid, new byte[] { 0x01 });
}
Because my BluetoothLEDevice, GattService, and GattCharacteristic objects are not disposed, all I need to do is resubscribe to notifications and write a 1 to the device so that it begins collecting data again.
I have reduced my memory allocations in my application significantly since discovering this, and the time for a gen2 collection has decreased to an average of 5 ms. Also, the amount of time before the connection disconnects has increased to around 4-5 sec.
UWP has a GattCharacteristicNotificationTrigger for receiving notifications in a BackgroundTask, but I have never had much success at incorporating background tasks in UWP.
I think I will try next to incorporate the windows.devices into a WPF application where I think I will have a better chance at getting it working.
So, after a while of trying different ideas I have finally stumbled across a solution to my problem. I had to make 2 changes:
Used the unpaired connection instead of the paired connection. This solved the problem of the connection dropping suddenly.
Increased the connection interval to 40 ms. For some reason when I did this, I received all of the data and no longer had any problems. Anything below 40 ms causes information to be lost when communicating to a Windows device (I had to make this change on the C code running on my sensors.)
I have used the devices for about 2 months now after making this change and have had no problems at all.
I seems to me that these problems are related to the BluetoothCacheMode Enum.
This indicates whether certain Bluetooth API methods should operate on values cached in the system or
retrieve those values from the Bluetooth device.
Using BluetoothCacheMode.Uncached attribute allows the service to update the attributes when needed.
If the device is paired then the BluetoothCacheMode is not needed(I think BluetoothCacheMode.Cached is default).
In your code the line:
var services = await leDevice.GetGattServicesAsync(BluetoothCacheMode.Uncached);
Can be the cause of the connection lost if paired.
GetGattServicesAsync(), GetCharacteristicsAsync() and ReadValueAsync()
must have the attribute BluetoothCacheMode.Uncached when not paired, when paired default or BluetoothCacheMode.Cached.
See https://msdn.microsoft.com/en-us/library/windows/apps/dn263758.aspx.
I have a UWP project want to take a snapshot from Mediaelement while playing the video.
Does anyone know any useful links or how to tackle this task?
For your requirement, you could realize it with Custom video effects. Because you could get per frame in ProcessFrame method. And you could use a static property to store current frame and pass it to your image control. The following is RExampleVidoEffect class.
public sealed class RExampleVidoEffect : IBasicVideoEffect
{
private static SoftwareBitmap Snap;
public void SetEncodingProperties(VideoEncodingProperties encodingProperties, IDirect3DDevice device)
{
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
var inputFrameBitmap = context.InputFrame.SoftwareBitmap;
Snap = inputFrameBitmap;
}
public static SoftwareBitmap GetSnapShot()
{
return Snap;
}
public void Close(MediaEffectClosedReason reason)
{
}
public void DiscardQueuedFrames()
{
}
public bool IsReadOnly
{
get
{
return true;
}
}
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get { return new List<VideoEncodingProperties>(); }
}
public MediaMemoryTypes SupportedMemoryTypes
{
get { return MediaMemoryTypes.Cpu; }
}
public bool TimeIndependent
{
get { return true; }
}
public void SetProperties(IPropertySet configuration)
{
}
}
Usage
private async void VideoPlayer_Loaded(object sender, RoutedEventArgs e)
{
var videoFile = await Package.Current.InstalledLocation.GetFileAsync("big_buck_bunny.mp4");
MediaClip clip = await MediaClip.CreateFromFileAsync(videoFile);
var videoEffectDefinition = new VideoEffectDefinition(typeof(RExampleVidoEffect).FullName);
clip.VideoEffectDefinitions.Add(videoEffectDefinition);
MediaComposition compositor = new MediaComposition();
compositor.Clips.Add(clip);
this.VideoPlayer.SetMediaStreamSource(compositor.GenerateMediaStreamSource());
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
var bitmap = RExampleVidoEffect.GetSnapShot();
if (bitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 ||
bitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
{
bitmap = SoftwareBitmap.Convert(bitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
}
var source = new SoftwareBitmapSource();
await source.SetBitmapAsync(bitmap);
img.Source = source;
}
Effect
Figure 1 - Working Demo
I'm running a winforms application and I have implemented a status / progress TreeView. It can display the status (by icon) and progress of a (possibly hierarchical) set of tasks to accomplish. My question is not about how to implement the TreeView control itself. I've got that part covered. The TreeView is merely the way the background work is going to status / progress itself to the user.
I have a set of methods that I want to run on not the main UI thread. They need to run in order. They are steps in a larger process. I could organize them into a hierarchy; that would make a nice tree structure.
Each of these methods will be represented by a node in the tree. I probably got the idea for this method of visualization from the old Sql Server DTS status panel. I still like that idea.
I want to know when each method finishes and it's outcome, and possibly a few textual statuses along the way. I also want a general mechanism that I can use to bubble up progress. I will use those to do an owner drawn progress bar in the TreeView on the node that corresponds to that method.
I've read up some on multi-threading, and also on the Task class, but don't really understand it fully. And I don't care if the solution uses that or not. But maybe that would be more elegant I don't know. It seems more straight forward than call backs but maybe you know better.
The Task Class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Serialization;
using System.Linq;
namespace DeveloperWorkbench.Nodes
{
public class Task : INode
{
public delegate void TaskStatusDelegate(Task sender, TaskStatus taskStatus);
public event ProgressDelegate ProgressChanged;
public event StatusDelegate Status;
public event TaskStatusDelegate TaskStatusChanged;
public Task()
{
_children = new List<Task>();
}
[XmlIgnore()]
public bool CanHaveChildren { get; private set; }
private List<Task> _children;
public List<Task> Children
{
get
{
_children.ForEach(x => x.Parent = this);
return _children;
}
set
{
_children = value;
_children.ForEach(x => x.Parent = this);
}
}
[XmlIgnore()]
public List<string> ChildTypes { get; private set; }
public string FullName { get; set; }
private float _maxProgress = 0;
[Browsable(false)]
[XmlIgnore()]
public float MaxProgress
{
get { return _maxProgress; }
set
{
_maxProgress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
private Delegate _method;
[Browsable(false)]
[XmlIgnore()]
public Delegate Method
{
get { return _method; }
set
{
if (_method == value) return;
_method = value;
Name = Method.Method.Name;
TypeName = Method.Method.ReflectedType.FullName;
}
}
private string _name;
[ReadOnly(true)]
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
//Method = GetMethodByName(???, _name);
FullName = ProperCaseToSpaces(_name);
}
}
[Browsable(false)]
[XmlIgnore()]
public INode Parent { get; set; }
private float _progress = 0;
[Browsable(false)]
[XmlIgnore()]
public float Progress
{
get { return _progress; }
set
{
_progress = value;
RaiseProgress(this, Progress, MaxProgress);
}
}
public List<KeyValuePair<string, object>> RelatedItems { get; set; }
private TaskStatus _taskStatus = TaskStatus.Created;
[Browsable(false)]
[XmlIgnore()]
public TaskStatus TaskStatus
{
get { return _taskStatus; }
set
{
_taskStatus = value;
TaskStatusChanged(this, _taskStatus);
}
}
[ReadOnly(true)]
public string TypeName { get; set; }
public bool Visited { get; set; }
public Task Add(Task child)
{
Children.Add(child);
child.Parent = this;
child.ProgressChanged += Child_Progress;
return child;
}
private void Done(System.Threading.Tasks.Task task)
{
TaskStatus = TaskStatus.RanToCompletion;
}
public void Execute()
{
Progress = 0;
TaskStatus = TaskStatus.Running;
var systemTask = new System.Threading.Tasks.Task((Action)Method);
systemTask.ContinueWith(Done);
systemTask.Start();
if (Parent != null)
systemTask.Wait();
}
private static string ProperCaseToSpaces(string text)
{
return Regex.Replace(text, #"(\B[A-Z]+?(?=[A-Z][^A-Z])|\B[A-Z]+?(?=[^A-Z]))", " $1");
}
public void RaiseProgress(INode sender, float progress = 0, float maxProgress = 100)
{
ProgressChanged(sender, progress, maxProgress);
}
public void RaiseStatus(string status = "Ready")
{
Status(status);
}
public void Refresh(bool force)
{
throw new NotImplementedException();
}
public void RefreshChildren(bool force, string childType = null)
{
throw new NotImplementedException();
}
public List<KeyValuePair<string, INode>> RefreshRelatedItems(bool force)
{
throw new NotImplementedException();
}
//Usage: myTask.SetMethod(() => MyMethod(0, 40));
public void SetMethod(Action method)
{
Method = method;
}
//Usage: myTask.SetMethod(() => MyFunction(myArgument));
public void SetMethod<T>(Func<T> function)
{
Method = function;
}
public void SetMethod(Object target)
{
if (target.GetType().FullName == TypeName)
Method = GetMethodByName(target, Name);
else
{
var name = Name;
SetMethod(() => FakeExecute(this));
Name = name;
TypeName = null;
}
foreach (var child in Children)
{
child.SetMethod(target);
}
}
public void Child_Progress(INode sender, float progress = 0, float maxProgress = 100)
{
MaxProgress = _children.Sum(x => x.MaxProgress);
Progress = _children.Sum(x => x.Progress);
}
public static Task Create<T>(Func<T> method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(Action method, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(method);
return task;
}
public static Task Create(string methodName, Task parent = null)
{
var task = InnerCreate(parent);
task.SetMethod(() => FakeExecute(task));
task.Name = methodName;
task.TypeName = null;
return task;
}
private static Task InnerCreate(Task parent)
{
var task = new Task();
if (parent != null)
parent.Add(task);
return task;
}
public static Task CurrentTask(Task rootTask, int stackFrame = 1)
{
var taskMethodName = new StackFrame(stackFrame).GetMethod().Name;
return Find(rootTask, taskMethodName);
}
private static void FakeExecute(Task task)
{
foreach (Task child in task.Children)
{
child.MaxProgress = 100;
child.Progress = 0;
child.TaskStatus = TaskStatus.WaitingToRun;
}
foreach (Task child in task.Children)
{
child.Execute();
}
}
private static Task Find(Task task, string methodName)
{
return task.Method.Method.Name == methodName ?
task :
task.Children.Select(child => Find(child, methodName)).FirstOrDefault(found => found != null);
}
static Delegate GetMethodByName(object target, string methodName)
{
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
MethodInfo method = target.GetType().GetMethod(methodName, bindingFlags);
return method.ReturnType == typeof(void) ? Delegate.CreateDelegate(typeof(Action), target, method) : null;
}
}
}
The StatusList class:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Windows.Forms;
using Retalix.R10.DeveloperWorkbench.Nodes;
using Retalix.R10.DeveloperWorkbench.UI.Helpers;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI.Controls
{
public partial class StatusList : UserControl
{
// Import the SetWindowRgn function from the user32.DLL
// From the Unmanaged Code
[DllImport("user32.DLL", EntryPoint = "SetWindowRgn")]
private static extern int SetWindowRgn(int hWnd, int hRgn, int bRedraw);
[System.Runtime.InteropServices.DllImport("Gdi32.dll", EntryPoint = "CreateRoundRectRgn")]
private static extern System.IntPtr CreateRoundRectRgn
(
int nLeftRect, // x-coordinate of upper-left corner
int nTopRect, // y-coordinate of upper-left corner
int nRightRect, // x-coordinate of lower-right corner
int nBottomRect, // y-coordinate of lower-right corner
int nWidthEllipse, // height of ellipse
int nHeightEllipse // width of ellipse
);
[System.Runtime.InteropServices.DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
private static extern bool DeleteObject(System.IntPtr hObject);
public StatusList()
{
InitializeComponent();
}
private TreeNode Add(TreeNodeCollection nodes, string text, string imageKey, object tag)
{
var treeNode = nodes.Add(tag.GetHashCode().ToString(), text);
treeNode.Tag = tag;
treeNode.ImageKey = imageKey;
treeNode.SelectedImageKey = imageKey;
tvTreeView.ExpandAll();
return treeNode;
}
public TreeNode Add(Task task)
{
var nodes = tvTreeView.Nodes;
if (task.Parent != null)
nodes = Find(task.Parent).Nodes;
task.TaskStatusChanged += Task_TaskStatusChanged;
task.ProgressChanged += Task_Progress;
var treeNode = Add(nodes, task.FullName, StatusIcon(task.TaskStatus), task);
foreach(var child in task.Children)
{
Add(child);
}
return treeNode;
}
private TreeNode Find(object tag)
{
var treeNodes = tvTreeView.Nodes.Find(tag.GetHashCode().ToString(), true);
if (treeNodes.Length > 0)
return treeNodes[0];
return null;
}
private string StatusIcon(System.Threading.Tasks.TaskStatus status)
{
switch (status)
{
case TaskStatus.Canceled:
case TaskStatus.Created:
case TaskStatus.Faulted:
case TaskStatus.RanToCompletion:
return status.ToString();
break;
case TaskStatus.Running:
case TaskStatus.WaitingForChildrenToComplete:
return TaskStatus.Running.ToString();
break;
default:
if (status.ToString().StartsWith("Waiting"))
return "Waiting";
break;
}
return "Created";
}
private void tvTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
var task = (Task) e.Node.Tag;
if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
//e.Graphics.DrawRectangle(SystemPens.ControlDark, e.Bounds.Left, e.Bounds.Top , e.Bounds.Width - 1, e.Bounds.Height - 1);
}
if(task.TaskStatus == TaskStatus.Running)
{
var borderBrush = new LinearGradientBrush(new Point(e.Bounds.Left + 1, e.Bounds.Top + 3), new Point(e.Bounds.Left + 1, e.Bounds.Bottom), Color.White, Color.FromArgb(200, Color.LightGray));
var borderRectangle = new Rectangle(e.Bounds.Left + 1, e.Bounds.Top + 3, e.Bounds.Width - 10, e.Bounds.Height - 6);
var borderGraphicsPath = RoundedRectangle.Create(borderRectangle);
e.Graphics.FillPath(borderBrush, borderGraphicsPath);
e.Graphics.DrawPath(Pens.DarkGray, borderGraphicsPath);
//e.Graphics.FillRectangle(borderBrush, borderRectangle);
//e.Graphics.DrawRectangle(pen, borderRectangle);
if (task.Progress > 0)
{
//pen.DashStyle = DashStyle.Dot;
var width = (task.Progress / task.MaxProgress) * (e.Bounds.Width - 11);
var progressRectangle = new Rectangle(e.Bounds.Left + 2, e.Bounds.Top + 4, (int)width, e.Bounds.Height - 7);
var progressGraphicsPath = RoundedRectangle.Create(progressRectangle, 5, RoundedRectangle.RectangleCorners.TopLeft | RoundedRectangle.RectangleCorners.BottomLeft);
//e.Graphics.DrawRectangle(pen, rectangle);
var progressBrush = new LinearGradientBrush(new Point(progressRectangle.Left, progressRectangle.Top - 1), new Point(progressRectangle.Left, progressRectangle.Bottom), Color.White, Color.LimeGreen);
e.Graphics.FillPath(progressBrush, progressGraphicsPath);
//e.Graphics.FillRectangle(progressLinearGradientBrush, progressRectangle);
//GraphicsPath path = RoundedRectangle.Create(rectangle);
//e.Graphics.DrawPath(Pens.Black, path);
//System.IntPtr ptrBorder = CreateRoundRectRgn(e.Bounds.Left, e.Bounds.Top, e.Bounds.Left + 50, e.Bounds.Bottom, 5, 5);
//try { SetWindowRgn(tvTreeView.Handle.ToInt32(), ptrBorder.ToInt32(), 1) ; }
//finally { DeleteObject(ptrBorder); }
}
}
var textSize = e.Graphics.MeasureString(task.Name, tvTreeView.Font);
var controlText = SystemBrushes.ControlText;
e.Graphics.DrawString(task.Name, tvTreeView.Font, controlText, e.Bounds.Left - 1, e.Bounds.Top + e.Bounds.Height / 2f - textSize.Height / 2f);
//if ((e.State & TreeNodeStates.Selected) == TreeNodeStates.Selected)
// controlText = SystemBrushes.HighlightText;
}
public void Task_Progress(Nodes.INode sender, float progress = 0, float maxProgress = 100)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new ProgressDelegate(Task_Progress), sender, progress, maxProgress);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
tvTreeView.Invalidate(treeNode.Bounds);
}
}
}
public void Task_TaskStatusChanged(Task sender, TaskStatus taskStatus)
{
if (IsDisposed) return;
if (InvokeRequired)
{
Invoke(new Task.TaskStatusDelegate(Task_TaskStatusChanged), sender, taskStatus);
}
else
{
if (tvTreeView.IsDisposed) return;
var treeNode = Find(sender);
if (treeNode != null)
{
treeNode.ImageKey = StatusIcon(taskStatus);
treeNode.SelectedImageKey = treeNode.ImageKey;
}
}
}
}
}
And how it's used:
using System;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using Task = Retalix.R10.DeveloperWorkbench.Nodes.Task;
namespace DeveloperWorkbench.UI
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
BuildTaskHierarchy();
}
private Task _rootTask;
public void BuildTaskHierarchy()
{
var roottaskXml = #"c:\temp\roottask.xml";
if (File.Exists(roottaskXml))
{
//method hierarchy can be deserialized...
_rootTask = (Task)Serialization.Deserialize(typeof(Task), roottaskXml);
_rootTask.SetMethod(target: this);
}
else
{
//...or constructed from scratch
_rootTask = Task.Create("Avert War With The Klingons");
Task.Create(GetToTheEnterprise, _rootTask);
var taskC = Task.Create("Kill General Chang", _rootTask);
Task.Create(FindThatThingsTailpipe, taskC);
Task.Create(TargetThatExplosionAndFire, taskC);
Task.Create(ThwartCampKhitomerAssassination, _rootTask);
Task.Create(ExplainToHighCommand, _rootTask);
Serialization.Serialize(_rootTask, roottaskXml);
}
statusList1.Add(_rootTask);
}
private void GetToTheEnterprise()
{
LongOp();
}
private void FindThatThingsTailpipe()
{
LongOp();
}
private void TargetThatExplosionAndFire()
{
LongOp();
}
private void ThwartCampKhitomerAssassination()
{
LongOp();
}
private void ExplainToHighCommand()
{
LongOp();
}
private void LongOp()
{
var task = Task.CurrentTask(_rootTask, 2);
task.MaxProgress = 100;
for (var i = 0; i <= 50; i++)
{
task.Progress = i*2;
Thread.Sleep(25);
}
}
private void button1_Click(object sender, EventArgs e)
{
_rootTask.Execute();
}
}
}
I'm just posting my progress. I have tested this in my actual application and it's working. I still need a convenience function to raise progress from within any method. I am still looking for feedback about how I can reduce the instrumentation required here. I want the least invasive strategy possible. Something that watches the call chain at run time would be an awesome addition.
The Progress class makes updating a UI with progress quite easy.
Just create a progress instance from within your UI; something that can take whatever information the background process currently has an update the UI appropriately:
Progress<Tuple<Operation, int>> progress = new Progress<Tuple<Operation, int>>();
progress.ProgressChanged += (_, info) =>
{
TreeView node = GetTreeviewFromOperation(info.Item1);
UpdateNodeWithProgress(node, info.Item2);
};
You can adjust that to whatever your circumstances are. Presumably the background process will have some sort of type that represents an operation, and you can map that back to the tree node that represents it. You could also pass in any other info you need to use to update the UI. If you have a lot of information to pass consider creating a new named type to represent it, rather than just using a Tuple as I did here.
Then just pass the progress to your background process, whatever that is (it could be a new thread, a task, the callback of an asynchronous method, or whatever).
//this is the work to do in the background
public static void DoWork(IProgress<Tuple<Operation, int>> progress)
{
Thread.Sleep(1000); //placeholder for real work
progress.Report(something, 50);
}
//start that work in a new task; call from the UI thread
//right after creating the `Progress` instance
Task.Run(()=> DoWork(progress));
If you don't have .NET 4.5 you can create your own version of this class rather easily:
public interface IProgress<T>
{
void Report(T data);
}
public class Progress<T> : IProgress<T>
{
SynchronizationContext context;
public Progress()
{
context = SynchronizationContext.Current
?? new SynchronizationContext();
}
public Progress(Action<T> action)
: this()
{
ProgressReported += action;
}
public event Action<T> ProgressReported;
void IProgress<T>.Report(T data)
{
var action = ProgressReported;
if (action != null)
{
context.Post(arg => action((T)arg), data);
}
}
}
Read up on the BackgroundWorker class. It's an old but very simple way to do background work without going into the complexities of threading.
All you got to do is create one, handle its DoWork event to perform your logic (which will run in the background) and pass progress back to the main ui thread through its ReportProgress function which you will then handle to update the ui of your tree however you like.
The best option to update UI is to leave the synchronisation responsibilities to .NET itself. Make use of async and await that helps to switch to UI thread from background thread when the Task is completed.
This library solves the exact purpose that you require.
Take a look at the examples WPF and Blazor here.
NUGET package here.